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.
Files changed (60) hide show
  1. code_puppy/agent.py +15 -17
  2. code_puppy/agents/agent_manager.py +320 -9
  3. code_puppy/agents/base_agent.py +58 -2
  4. code_puppy/agents/runtime_manager.py +68 -42
  5. code_puppy/command_line/command_handler.py +82 -33
  6. code_puppy/command_line/mcp/__init__.py +10 -0
  7. code_puppy/command_line/mcp/add_command.py +183 -0
  8. code_puppy/command_line/mcp/base.py +35 -0
  9. code_puppy/command_line/mcp/handler.py +133 -0
  10. code_puppy/command_line/mcp/help_command.py +146 -0
  11. code_puppy/command_line/mcp/install_command.py +176 -0
  12. code_puppy/command_line/mcp/list_command.py +94 -0
  13. code_puppy/command_line/mcp/logs_command.py +126 -0
  14. code_puppy/command_line/mcp/remove_command.py +82 -0
  15. code_puppy/command_line/mcp/restart_command.py +92 -0
  16. code_puppy/command_line/mcp/search_command.py +117 -0
  17. code_puppy/command_line/mcp/start_all_command.py +126 -0
  18. code_puppy/command_line/mcp/start_command.py +98 -0
  19. code_puppy/command_line/mcp/status_command.py +185 -0
  20. code_puppy/command_line/mcp/stop_all_command.py +109 -0
  21. code_puppy/command_line/mcp/stop_command.py +79 -0
  22. code_puppy/command_line/mcp/test_command.py +107 -0
  23. code_puppy/command_line/mcp/utils.py +129 -0
  24. code_puppy/command_line/mcp/wizard_utils.py +259 -0
  25. code_puppy/command_line/model_picker_completion.py +21 -4
  26. code_puppy/command_line/prompt_toolkit_completion.py +9 -0
  27. code_puppy/main.py +23 -17
  28. code_puppy/mcp/__init__.py +42 -16
  29. code_puppy/mcp/async_lifecycle.py +51 -49
  30. code_puppy/mcp/blocking_startup.py +125 -113
  31. code_puppy/mcp/captured_stdio_server.py +63 -70
  32. code_puppy/mcp/circuit_breaker.py +63 -47
  33. code_puppy/mcp/config_wizard.py +169 -136
  34. code_puppy/mcp/dashboard.py +79 -71
  35. code_puppy/mcp/error_isolation.py +147 -100
  36. code_puppy/mcp/examples/retry_example.py +55 -42
  37. code_puppy/mcp/health_monitor.py +152 -141
  38. code_puppy/mcp/managed_server.py +100 -97
  39. code_puppy/mcp/manager.py +168 -156
  40. code_puppy/mcp/registry.py +148 -110
  41. code_puppy/mcp/retry_manager.py +63 -61
  42. code_puppy/mcp/server_registry_catalog.py +271 -225
  43. code_puppy/mcp/status_tracker.py +80 -80
  44. code_puppy/mcp/system_tools.py +47 -52
  45. code_puppy/messaging/message_queue.py +20 -13
  46. code_puppy/messaging/renderers.py +30 -15
  47. code_puppy/state_management.py +103 -0
  48. code_puppy/tui/app.py +64 -7
  49. code_puppy/tui/components/chat_view.py +3 -3
  50. code_puppy/tui/components/human_input_modal.py +12 -8
  51. code_puppy/tui/screens/__init__.py +2 -2
  52. code_puppy/tui/screens/mcp_install_wizard.py +208 -179
  53. code_puppy/tui/tests/test_agent_command.py +3 -3
  54. {code_puppy-0.0.134.dist-info → code_puppy-0.0.136.dist-info}/METADATA +1 -1
  55. {code_puppy-0.0.134.dist-info → code_puppy-0.0.136.dist-info}/RECORD +59 -41
  56. code_puppy/command_line/mcp_commands.py +0 -1789
  57. {code_puppy-0.0.134.data → code_puppy-0.0.136.data}/data/code_puppy/models.json +0 -0
  58. {code_puppy-0.0.134.dist-info → code_puppy-0.0.136.dist-info}/WHEEL +0 -0
  59. {code_puppy-0.0.134.dist-info → code_puppy-0.0.136.dist-info}/entry_points.txt +0 -0
  60. {code_puppy-0.0.134.dist-info → code_puppy-0.0.136.dist-info}/licenses/LICENSE +0 -0
@@ -1,40 +1,52 @@
1
1
  """
2
2
  MCP Configuration Wizard - Interactive setup for MCP servers.
3
+
4
+ Note: This module imports ServerConfig and get_mcp_manager directly from
5
+ .code_puppy.mcp.manager to avoid circular imports with the package __init__.py
3
6
  """
4
7
 
5
8
  import re
6
9
  from typing import Dict, Optional
7
10
  from urllib.parse import urlparse
8
11
 
9
- from code_puppy.mcp import ServerConfig, get_mcp_manager
10
- from code_puppy.messaging import emit_error, emit_info, emit_success, emit_warning, emit_prompt
11
12
  from rich.console import Console
12
13
 
14
+ from code_puppy.mcp.manager import ServerConfig, get_mcp_manager
15
+ from code_puppy.messaging import (
16
+ emit_error,
17
+ emit_info,
18
+ emit_prompt,
19
+ emit_success,
20
+ emit_warning,
21
+ )
22
+
13
23
  console = Console()
14
24
 
15
25
 
16
- def prompt_ask(prompt_text: str, default: Optional[str] = None, choices: Optional[list] = None) -> Optional[str]:
26
+ def prompt_ask(
27
+ prompt_text: str, default: Optional[str] = None, choices: Optional[list] = None
28
+ ) -> Optional[str]:
17
29
  """Helper function to replace rich.prompt.Prompt.ask with emit_prompt."""
18
30
  try:
19
31
  if default:
20
32
  full_prompt = f"{prompt_text} [{default}]"
21
33
  else:
22
34
  full_prompt = prompt_text
23
-
35
+
24
36
  if choices:
25
37
  full_prompt += f" ({'/'.join(choices)})"
26
-
38
+
27
39
  response = emit_prompt(full_prompt + ": ")
28
-
40
+
29
41
  # Handle default value
30
42
  if not response.strip() and default:
31
43
  return default
32
-
44
+
33
45
  # Handle choices validation
34
46
  if choices and response.strip() and response.strip() not in choices:
35
47
  emit_error(f"Invalid choice. Must be one of: {', '.join(choices)}")
36
48
  return None
37
-
49
+
38
50
  return response.strip() if response.strip() else None
39
51
  except Exception as e:
40
52
  emit_error(f"Input error: {e}")
@@ -46,14 +58,14 @@ def confirm_ask(prompt_text: str, default: bool = True) -> bool:
46
58
  try:
47
59
  default_text = "[Y/n]" if default else "[y/N]"
48
60
  response = emit_prompt(f"{prompt_text} {default_text}: ")
49
-
61
+
50
62
  if not response.strip():
51
63
  return default
52
-
64
+
53
65
  response_lower = response.strip().lower()
54
- if response_lower in ['y', 'yes', 'true', '1']:
66
+ if response_lower in ["y", "yes", "true", "1"]:
55
67
  return True
56
- elif response_lower in ['n', 'no', 'false', '0']:
68
+ elif response_lower in ["n", "no", "false", "0"]:
57
69
  return False
58
70
  else:
59
71
  return default
@@ -64,36 +76,37 @@ def confirm_ask(prompt_text: str, default: bool = True) -> bool:
64
76
 
65
77
  class MCPConfigWizard:
66
78
  """Interactive wizard for configuring MCP servers."""
67
-
79
+
68
80
  def __init__(self):
69
81
  self.manager = get_mcp_manager()
70
-
82
+
71
83
  def run_wizard(self, group_id: str = None) -> Optional[ServerConfig]:
72
84
  """
73
85
  Run the interactive configuration wizard.
74
-
86
+
75
87
  Args:
76
88
  group_id: Optional message group ID for grouping related messages
77
-
89
+
78
90
  Returns:
79
91
  ServerConfig if successful, None if cancelled
80
92
  """
81
93
  if group_id is None:
82
94
  import uuid
95
+
83
96
  group_id = str(uuid.uuid4())
84
-
97
+
85
98
  emit_info("🧙 MCP Server Configuration Wizard", message_group=group_id)
86
-
99
+
87
100
  # Step 1: Server name
88
101
  name = self.prompt_server_name(group_id)
89
102
  if not name:
90
103
  return None
91
-
104
+
92
105
  # Step 2: Server type
93
106
  server_type = self.prompt_server_type(group_id)
94
107
  if not server_type:
95
108
  return None
96
-
109
+
97
110
  # Step 3: Type-specific configuration
98
111
  config = {}
99
112
  if server_type == "sse":
@@ -102,268 +115,273 @@ class MCPConfigWizard:
102
115
  config = self.prompt_http_config(group_id)
103
116
  elif server_type == "stdio":
104
117
  config = self.prompt_stdio_config(group_id)
105
-
118
+
106
119
  if not config:
107
120
  return None
108
-
121
+
109
122
  # Step 4: Create ServerConfig
110
123
  server_config = ServerConfig(
111
124
  id=f"{name}_{hash(name)}",
112
125
  name=name,
113
126
  type=server_type,
114
127
  enabled=True,
115
- config=config
128
+ config=config,
116
129
  )
117
-
130
+
118
131
  # Step 5: Show summary and confirm
119
132
  if self.prompt_confirmation(server_config, group_id):
120
133
  return server_config
121
-
134
+
122
135
  return None
123
-
136
+
124
137
  def prompt_server_name(self, group_id: str = None) -> Optional[str]:
125
138
  """Prompt for server name with validation."""
126
139
  while True:
127
140
  name = prompt_ask("Enter server name", default=None)
128
-
141
+
129
142
  if not name:
130
143
  if not confirm_ask("Cancel configuration?", default=False):
131
144
  continue
132
145
  return None
133
-
146
+
134
147
  # Validate name
135
148
  if not self.validate_name(name):
136
- emit_error("Name must be alphanumeric with hyphens/underscores only", message_group=group_id)
149
+ emit_error(
150
+ "Name must be alphanumeric with hyphens/underscores only",
151
+ message_group=group_id,
152
+ )
137
153
  continue
138
-
154
+
139
155
  # Check uniqueness
140
156
  existing = self.manager.registry.get_by_name(name)
141
157
  if existing:
142
158
  emit_error(f"Server '{name}' already exists", message_group=group_id)
143
159
  continue
144
-
160
+
145
161
  return name
146
-
162
+
147
163
  def prompt_server_type(self, group_id: str = None) -> Optional[str]:
148
164
  """Prompt for server type."""
149
165
  emit_info("\nServer types:", message_group=group_id)
150
- emit_info(" sse - Server-Sent Events (HTTP streaming)", message_group=group_id)
151
- emit_info(" http - HTTP/REST API", message_group=group_id)
166
+ emit_info(
167
+ " sse - Server-Sent Events (HTTP streaming)", message_group=group_id
168
+ )
169
+ emit_info(" http - HTTP/REST API", message_group=group_id)
152
170
  emit_info(" stdio - Local command (subprocess)", message_group=group_id)
153
-
171
+
154
172
  while True:
155
- server_type = prompt_ask("Select server type", choices=["sse", "http", "stdio"], default="stdio")
156
-
173
+ server_type = prompt_ask(
174
+ "Select server type", choices=["sse", "http", "stdio"], default="stdio"
175
+ )
176
+
157
177
  if server_type in ["sse", "http", "stdio"]:
158
178
  return server_type
159
-
160
- emit_error("Invalid type. Choose: sse, http, or stdio", message_group=group_id)
161
-
179
+
180
+ emit_error(
181
+ "Invalid type. Choose: sse, http, or stdio", message_group=group_id
182
+ )
183
+
162
184
  def prompt_sse_config(self, group_id: str = None) -> Optional[Dict]:
163
185
  """Prompt for SSE server configuration."""
164
186
  emit_info("Configuring SSE server", message_group=group_id)
165
-
187
+
166
188
  # URL
167
189
  url = self.prompt_url("SSE", group_id)
168
190
  if not url:
169
191
  return None
170
-
171
- config = {
172
- "type": "sse",
173
- "url": url,
174
- "timeout": 30
175
- }
176
-
192
+
193
+ config = {"type": "sse", "url": url, "timeout": 30}
194
+
177
195
  # Headers (optional)
178
196
  if confirm_ask("Add custom headers?", default=False):
179
197
  headers = self.prompt_headers(group_id)
180
198
  if headers:
181
199
  config["headers"] = headers
182
-
200
+
183
201
  # Timeout
184
202
  timeout_str = prompt_ask("Connection timeout (seconds)", default="30")
185
203
  try:
186
204
  config["timeout"] = int(timeout_str)
187
205
  except ValueError:
188
206
  config["timeout"] = 30
189
-
207
+
190
208
  return config
191
-
209
+
192
210
  def prompt_http_config(self, group_id: str = None) -> Optional[Dict]:
193
211
  """Prompt for HTTP server configuration."""
194
212
  emit_info("Configuring HTTP server", message_group=group_id)
195
-
213
+
196
214
  # URL
197
215
  url = self.prompt_url("HTTP", group_id)
198
216
  if not url:
199
217
  return None
200
-
201
- config = {
202
- "type": "http",
203
- "url": url,
204
- "timeout": 30
205
- }
206
-
218
+
219
+ config = {"type": "http", "url": url, "timeout": 30}
220
+
207
221
  # Headers (optional)
208
222
  if confirm_ask("Add custom headers?", default=False):
209
223
  headers = self.prompt_headers(group_id)
210
224
  if headers:
211
225
  config["headers"] = headers
212
-
226
+
213
227
  # Timeout
214
228
  timeout_str = prompt_ask("Request timeout (seconds)", default="30")
215
229
  try:
216
230
  config["timeout"] = int(timeout_str)
217
231
  except ValueError:
218
232
  config["timeout"] = 30
219
-
233
+
220
234
  return config
221
-
235
+
222
236
  def prompt_stdio_config(self, group_id: str = None) -> Optional[Dict]:
223
237
  """Prompt for Stdio server configuration."""
224
238
  emit_info("Configuring Stdio server", message_group=group_id)
225
239
  emit_info("Examples:", message_group=group_id)
226
- emit_info(" • npx -y @modelcontextprotocol/server-filesystem /path", message_group=group_id)
227
- emit_info(" • python mcp_server.py", message_group=group_id)
240
+ emit_info(
241
+ " • npx -y @modelcontextprotocol/server-filesystem /path",
242
+ message_group=group_id,
243
+ )
244
+ emit_info(" • python mcp_server.py", message_group=group_id)
228
245
  emit_info(" • node server.js", message_group=group_id)
229
-
246
+
230
247
  # Command
231
248
  command = prompt_ask("Enter command", default=None)
232
-
249
+
233
250
  if not command:
234
251
  return None
235
-
236
- config = {
237
- "type": "stdio",
238
- "command": command,
239
- "args": [],
240
- "timeout": 30
241
- }
242
-
252
+
253
+ config = {"type": "stdio", "command": command, "args": [], "timeout": 30}
254
+
243
255
  # Arguments
244
256
  args_str = prompt_ask("Enter arguments (space-separated)", default="")
245
257
  if args_str:
246
258
  # Simple argument parsing (handles quoted strings)
247
259
  import shlex
260
+
248
261
  try:
249
262
  config["args"] = shlex.split(args_str)
250
263
  except ValueError:
251
264
  config["args"] = args_str.split()
252
-
265
+
253
266
  # Working directory (optional)
254
267
  cwd = prompt_ask("Working directory (optional)", default="")
255
268
  if cwd:
256
269
  import os
270
+
257
271
  if os.path.isdir(os.path.expanduser(cwd)):
258
272
  config["cwd"] = os.path.expanduser(cwd)
259
273
  else:
260
- emit_warning(f"Directory '{cwd}' not found, ignoring", message_group=group_id)
261
-
274
+ emit_warning(
275
+ f"Directory '{cwd}' not found, ignoring", message_group=group_id
276
+ )
277
+
262
278
  # Environment variables (optional)
263
279
  if confirm_ask("Add environment variables?", default=False):
264
280
  env = self.prompt_env_vars(group_id)
265
281
  if env:
266
282
  config["env"] = env
267
-
283
+
268
284
  # Timeout
269
285
  timeout_str = prompt_ask("Startup timeout (seconds)", default="30")
270
286
  try:
271
287
  config["timeout"] = int(timeout_str)
272
288
  except ValueError:
273
289
  config["timeout"] = 30
274
-
290
+
275
291
  return config
276
-
292
+
277
293
  def prompt_url(self, server_type: str, group_id: str = None) -> Optional[str]:
278
294
  """Prompt for and validate URL."""
279
295
  while True:
280
296
  url = prompt_ask(f"Enter {server_type} server URL", default=None)
281
-
297
+
282
298
  if not url:
283
299
  if confirm_ask("Cancel configuration?", default=False):
284
300
  return None
285
301
  continue
286
-
302
+
287
303
  if self.validate_url(url):
288
304
  return url
289
-
290
- emit_error("Invalid URL. Must be http:// or https://", message_group=group_id)
291
-
305
+
306
+ emit_error(
307
+ "Invalid URL. Must be http:// or https://", message_group=group_id
308
+ )
309
+
292
310
  def prompt_headers(self, group_id: str = None) -> Dict[str, str]:
293
311
  """Prompt for HTTP headers."""
294
312
  headers = {}
295
313
  emit_info("Enter headers (format: Name: Value)", message_group=group_id)
296
314
  emit_info("Press Enter with empty name to finish", message_group=group_id)
297
-
315
+
298
316
  while True:
299
317
  name = prompt_ask("Header name", default="")
300
318
  if not name:
301
319
  break
302
-
320
+
303
321
  value = prompt_ask(f"Value for '{name}'", default="")
304
322
  headers[name] = value
305
-
323
+
306
324
  if not confirm_ask("Add another header?", default=True):
307
325
  break
308
-
326
+
309
327
  return headers
310
-
328
+
311
329
  def prompt_env_vars(self, group_id: str = None) -> Dict[str, str]:
312
330
  """Prompt for environment variables."""
313
331
  env = {}
314
332
  emit_info("Enter environment variables", message_group=group_id)
315
333
  emit_info("Press Enter with empty name to finish", message_group=group_id)
316
-
334
+
317
335
  while True:
318
336
  name = prompt_ask("Variable name", default="")
319
337
  if not name:
320
338
  break
321
-
339
+
322
340
  value = prompt_ask(f"Value for '{name}'", default="")
323
341
  env[name] = value
324
-
342
+
325
343
  if not confirm_ask("Add another variable?", default=True):
326
344
  break
327
-
345
+
328
346
  return env
329
-
347
+
330
348
  def validate_name(self, name: str) -> bool:
331
349
  """Validate server name."""
332
350
  # Allow alphanumeric, hyphens, and underscores
333
- return bool(re.match(r'^[a-zA-Z0-9_-]+$', name))
334
-
351
+ return bool(re.match(r"^[a-zA-Z0-9_-]+$", name))
352
+
335
353
  def validate_url(self, url: str) -> bool:
336
354
  """Validate URL format."""
337
355
  try:
338
356
  result = urlparse(url)
339
- return result.scheme in ('http', 'https') and bool(result.netloc)
357
+ return result.scheme in ("http", "https") and bool(result.netloc)
340
358
  except Exception:
341
359
  return False
342
-
360
+
343
361
  def validate_command(self, command: str) -> bool:
344
362
  """Check if command exists (basic check)."""
345
- import shutil
346
363
  import os
347
-
364
+ import shutil
365
+
348
366
  # If it's a path, check if file exists
349
- if '/' in command or '\\' in command:
367
+ if "/" in command or "\\" in command:
350
368
  return os.path.isfile(command)
351
-
369
+
352
370
  # Otherwise check if it's in PATH
353
371
  return shutil.which(command) is not None
354
-
372
+
355
373
  def test_connection(self, config: ServerConfig, group_id: str = None) -> bool:
356
374
  """
357
375
  Test connection to the configured server.
358
-
376
+
359
377
  Args:
360
378
  config: Server configuration to test
361
-
379
+
362
380
  Returns:
363
381
  True if connection successful, False otherwise
364
382
  """
365
383
  emit_info("Testing connection...", message_group=group_id)
366
-
384
+
367
385
  try:
368
386
  # Try to create the server instance
369
387
  managed = self.manager.get_server(config.id)
@@ -371,101 +389,116 @@ class MCPConfigWizard:
371
389
  # Temporarily register to test
372
390
  self.manager.register_server(config)
373
391
  managed = self.manager.get_server(config.id)
374
-
392
+
375
393
  if managed:
376
394
  # Try to get the pydantic server (this validates config)
377
395
  server = managed.get_pydantic_server()
378
396
  if server:
379
397
  emit_success("✓ Configuration valid", message_group=group_id)
380
398
  return True
381
-
399
+
382
400
  emit_error("✗ Failed to create server instance", message_group=group_id)
383
401
  return False
384
-
402
+
385
403
  except Exception as e:
386
404
  emit_error(f"✗ Configuration error: {e}", message_group=group_id)
387
405
  return False
388
-
406
+
389
407
  def prompt_confirmation(self, config: ServerConfig, group_id: str = None) -> bool:
390
408
  """Show summary and ask for confirmation."""
391
409
  emit_info("Configuration Summary:", message_group=group_id)
392
410
  emit_info(f" Name: {config.name}", message_group=group_id)
393
411
  emit_info(f" Type: {config.type}", message_group=group_id)
394
-
412
+
395
413
  if config.type in ["sse", "http"]:
396
414
  emit_info(f" URL: {config.config.get('url')}", message_group=group_id)
397
415
  elif config.type == "stdio":
398
- emit_info(f" Command: {config.config.get('command')}", message_group=group_id)
399
- args = config.config.get('args', [])
416
+ emit_info(
417
+ f" Command: {config.config.get('command')}", message_group=group_id
418
+ )
419
+ args = config.config.get("args", [])
400
420
  if args:
401
421
  emit_info(f" Arguments: {' '.join(args)}", message_group=group_id)
402
-
403
- emit_info(f" Timeout: {config.config.get('timeout', 30)}s", message_group=group_id)
404
-
422
+
423
+ emit_info(
424
+ f" Timeout: {config.config.get('timeout', 30)}s", message_group=group_id
425
+ )
426
+
405
427
  # Test connection if requested
406
428
  if confirm_ask("Test connection?", default=True):
407
429
  if not self.test_connection(config, group_id):
408
430
  if not confirm_ask("Continue anyway?", default=False):
409
431
  return False
410
-
432
+
411
433
  return confirm_ask("Save this configuration?", default=True)
412
434
 
413
435
 
414
436
  def run_add_wizard(group_id: str = None) -> bool:
415
437
  """
416
438
  Run the MCP add wizard and register the server.
417
-
439
+
418
440
  Args:
419
441
  group_id: Optional message group ID for grouping related messages
420
-
442
+
421
443
  Returns:
422
444
  True if server was added, False otherwise
423
445
  """
424
446
  if group_id is None:
425
447
  import uuid
448
+
426
449
  group_id = str(uuid.uuid4())
427
-
450
+
428
451
  wizard = MCPConfigWizard()
429
452
  config = wizard.run_wizard(group_id)
430
-
453
+
431
454
  if config:
432
455
  try:
433
456
  manager = get_mcp_manager()
434
457
  server_id = manager.register_server(config)
435
-
436
- emit_success(f"\n✅ Server '{config.name}' added successfully!", message_group=group_id)
458
+
459
+ emit_success(
460
+ f"\n✅ Server '{config.name}' added successfully!",
461
+ message_group=group_id,
462
+ )
437
463
  emit_info(f"Server ID: {server_id}", message_group=group_id)
438
464
  emit_info("Use '/mcp list' to see all servers", message_group=group_id)
439
- emit_info(f"Use '/mcp start {config.name}' to start the server", message_group=group_id)
440
-
465
+ emit_info(
466
+ f"Use '/mcp start {config.name}' to start the server",
467
+ message_group=group_id,
468
+ )
469
+
441
470
  # Also save to mcp_servers.json for persistence
442
- from code_puppy.config import MCP_SERVERS_FILE, load_mcp_server_configs
443
471
  import json
444
472
  import os
445
-
473
+
474
+ from code_puppy.config import MCP_SERVERS_FILE
475
+
446
476
  # Load existing configs
447
477
  if os.path.exists(MCP_SERVERS_FILE):
448
- with open(MCP_SERVERS_FILE, 'r') as f:
478
+ with open(MCP_SERVERS_FILE, "r") as f:
449
479
  data = json.load(f)
450
480
  servers = data.get("mcp_servers", {})
451
481
  else:
452
482
  servers = {}
453
483
  data = {"mcp_servers": servers}
454
-
484
+
455
485
  # Add new server
456
486
  servers[config.name] = config.config
457
-
487
+
458
488
  # Save back
459
489
  os.makedirs(os.path.dirname(MCP_SERVERS_FILE), exist_ok=True)
460
- with open(MCP_SERVERS_FILE, 'w') as f:
490
+ with open(MCP_SERVERS_FILE, "w") as f:
461
491
  json.dump(data, f, indent=2)
462
-
463
- emit_info(f"[dim]Configuration saved to {MCP_SERVERS_FILE}[/dim]", message_group=group_id)
492
+
493
+ emit_info(
494
+ f"[dim]Configuration saved to {MCP_SERVERS_FILE}[/dim]",
495
+ message_group=group_id,
496
+ )
464
497
  return True
465
-
498
+
466
499
  except Exception as e:
467
500
  emit_error(f"Failed to add server: {e}", message_group=group_id)
468
501
  return False
469
502
  else:
470
503
  emit_warning("Configuration cancelled", message_group=group_id)
471
- return False
504
+ return False