code-puppy 0.0.135__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 -93
  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.135.dist-info → code_puppy-0.0.136.dist-info}/METADATA +1 -1
  55. {code_puppy-0.0.135.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.135.data → code_puppy-0.0.136.data}/data/code_puppy/models.json +0 -0
  58. {code_puppy-0.0.135.dist-info → code_puppy-0.0.136.dist-info}/WHEEL +0 -0
  59. {code_puppy-0.0.135.dist-info → code_puppy-0.0.136.dist-info}/entry_points.txt +0 -0
  60. {code_puppy-0.0.135.dist-info → code_puppy-0.0.136.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,259 @@
1
+ """
2
+ MCP Interactive Wizard Utilities - Shared interactive installation wizard functions.
3
+
4
+ Provides interactive functionality for installing and configuring MCP servers.
5
+ """
6
+
7
+ import logging
8
+ from typing import Any, Dict, Optional
9
+
10
+ from code_puppy.messaging import emit_info, emit_prompt
11
+
12
+ # Configure logging
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ def run_interactive_install_wizard(manager, group_id: str) -> bool:
17
+ """
18
+ Run the interactive MCP server installation wizard.
19
+
20
+ Args:
21
+ manager: MCP manager instance
22
+ group_id: Message group ID for grouping related messages
23
+
24
+ Returns:
25
+ True if installation was successful, False otherwise
26
+ """
27
+ try:
28
+ # Show welcome message
29
+ emit_info("🚀 MCP Server Installation Wizard", message_group=group_id)
30
+ emit_info(
31
+ "This wizard will help you install pre-configured MCP servers",
32
+ message_group=group_id,
33
+ )
34
+ emit_info("", message_group=group_id)
35
+
36
+ # Let user select a server
37
+ selected_server = interactive_server_selection(group_id)
38
+ if not selected_server:
39
+ return False
40
+
41
+ # Get custom name
42
+ server_name = interactive_get_server_name(selected_server, group_id)
43
+ if not server_name:
44
+ return False
45
+
46
+ # Configure the server
47
+ return interactive_configure_server(
48
+ manager, selected_server, server_name, group_id
49
+ )
50
+
51
+ except ImportError:
52
+ emit_info("[red]Server catalog not available[/red]", message_group=group_id)
53
+ return False
54
+ except Exception as e:
55
+ logger.error(f"Error in interactive wizard: {e}")
56
+ emit_info(f"[red]Wizard error: {e}[/red]", message_group=group_id)
57
+ return False
58
+
59
+
60
+ def interactive_server_selection(group_id: str):
61
+ """
62
+ Interactive server selection from catalog.
63
+
64
+ Returns selected server or None if cancelled.
65
+ """
66
+ # This is a simplified version - the full implementation would have
67
+ # category browsing, search, etc. For now, we'll just show popular servers
68
+ try:
69
+ from code_puppy.mcp.server_registry_catalog import catalog
70
+
71
+ servers = catalog.get_popular(10)
72
+ if not servers:
73
+ emit_info(
74
+ "[red]No servers available in catalog[/red]", message_group=group_id
75
+ )
76
+ return None
77
+
78
+ emit_info("Popular MCP Servers:", message_group=group_id)
79
+ for i, server in enumerate(servers, 1):
80
+ indicators = []
81
+ if server.verified:
82
+ indicators.append("✓")
83
+ if server.popular:
84
+ indicators.append("⭐")
85
+
86
+ indicator_str = ""
87
+ if indicators:
88
+ indicator_str = " " + "".join(indicators)
89
+
90
+ emit_info(
91
+ f"{i:2}. {server.display_name}{indicator_str}", message_group=group_id
92
+ )
93
+ emit_info(f" {server.description[:80]}...", message_group=group_id)
94
+
95
+ choice = emit_prompt(
96
+ "Enter number (1-{}) or 'q' to quit: ".format(len(servers))
97
+ )
98
+
99
+ if choice.lower() == "q":
100
+ return None
101
+
102
+ try:
103
+ index = int(choice) - 1
104
+ if 0 <= index < len(servers):
105
+ return servers[index]
106
+ else:
107
+ emit_info("[red]Invalid selection[/red]", message_group=group_id)
108
+ return None
109
+ except ValueError:
110
+ emit_info("[red]Invalid input[/red]", message_group=group_id)
111
+ return None
112
+
113
+ except Exception as e:
114
+ logger.error(f"Error in server selection: {e}")
115
+ return None
116
+
117
+
118
+ def interactive_get_server_name(selected_server, group_id: str) -> Optional[str]:
119
+ """
120
+ Get custom server name from user.
121
+
122
+ Returns server name or None if cancelled.
123
+ """
124
+ default_name = selected_server.name
125
+ server_name = emit_prompt(f"Enter name for this server [{default_name}]: ").strip()
126
+
127
+ if not server_name:
128
+ server_name = default_name
129
+
130
+ return server_name
131
+
132
+
133
+ def interactive_configure_server(
134
+ manager, selected_server, server_name: str, group_id: str
135
+ ) -> bool:
136
+ """
137
+ Configure and install the selected server.
138
+
139
+ Returns True if successful, False otherwise.
140
+ """
141
+ try:
142
+ # Check if server already exists
143
+ from .utils import find_server_id_by_name
144
+
145
+ existing_server = find_server_id_by_name(manager, server_name)
146
+ if existing_server:
147
+ override = emit_prompt(
148
+ f"Server '{server_name}' already exists. Override? [y/N]: "
149
+ )
150
+ if not override.lower().startswith("y"):
151
+ emit_info("Installation cancelled", message_group=group_id)
152
+ return False
153
+
154
+ # For now, use defaults - a full implementation would collect env vars, etc.
155
+ # requirements = selected_server.get_requirements() # TODO: Use for validation
156
+ env_vars = {}
157
+ cmd_args = {}
158
+
159
+ # Show confirmation
160
+ emit_info(f"Installing: {selected_server.display_name}", message_group=group_id)
161
+ emit_info(f"Name: {server_name}", message_group=group_id)
162
+
163
+ confirm = emit_prompt("Proceed with installation? [Y/n]: ")
164
+ if confirm.lower().startswith("n"):
165
+ emit_info("Installation cancelled", message_group=group_id)
166
+ return False
167
+
168
+ # Install the server (simplified version)
169
+ return install_server_from_catalog(
170
+ manager, selected_server, server_name, env_vars, cmd_args, group_id
171
+ )
172
+
173
+ except Exception as e:
174
+ logger.error(f"Error configuring server: {e}")
175
+ emit_info(f"[red]Configuration error: {e}[/red]", message_group=group_id)
176
+ return False
177
+
178
+
179
+ def install_server_from_catalog(
180
+ manager,
181
+ selected_server,
182
+ server_name: str,
183
+ env_vars: Dict[str, Any],
184
+ cmd_args: Dict[str, Any],
185
+ group_id: str,
186
+ ) -> bool:
187
+ """
188
+ Install a server from the catalog with the given configuration.
189
+
190
+ Returns True if successful, False otherwise.
191
+ """
192
+ try:
193
+ import json
194
+ import os
195
+
196
+ from code_puppy.config import MCP_SERVERS_FILE
197
+ from code_puppy.mcp.managed_server import ServerConfig
198
+
199
+ # Create server configuration
200
+ config_dict = selected_server.get_config_template()
201
+
202
+ # Apply environment variables and command args
203
+ if env_vars:
204
+ config_dict.update(env_vars)
205
+ if cmd_args:
206
+ config_dict.update(cmd_args)
207
+
208
+ # Create ServerConfig
209
+ server_config = ServerConfig(
210
+ id=f"{server_name}_{hash(server_name)}",
211
+ name=server_name,
212
+ type=selected_server.type,
213
+ enabled=True,
214
+ config=config_dict,
215
+ )
216
+
217
+ # Register with manager
218
+ server_id = manager.register_server(server_config)
219
+
220
+ if not server_id:
221
+ emit_info(
222
+ "[red]Failed to register server with manager[/red]",
223
+ message_group=group_id,
224
+ )
225
+ return False
226
+
227
+ # Save to mcp_servers.json for persistence
228
+ if os.path.exists(MCP_SERVERS_FILE):
229
+ with open(MCP_SERVERS_FILE, "r") as f:
230
+ data = json.load(f)
231
+ servers = data.get("mcp_servers", {})
232
+ else:
233
+ servers = {}
234
+ data = {"mcp_servers": servers}
235
+
236
+ # Add new server
237
+ servers[server_name] = config_dict.copy()
238
+ servers[server_name]["type"] = selected_server.type
239
+
240
+ # Save back
241
+ os.makedirs(os.path.dirname(MCP_SERVERS_FILE), exist_ok=True)
242
+ with open(MCP_SERVERS_FILE, "w") as f:
243
+ json.dump(data, f, indent=2)
244
+
245
+ emit_info(
246
+ f"[green]✓ Successfully installed server: {server_name}[/green]",
247
+ message_group=group_id,
248
+ )
249
+ emit_info(
250
+ "Use '/mcp start {}' to start the server".format(server_name),
251
+ message_group=group_id,
252
+ )
253
+
254
+ return True
255
+
256
+ except Exception as e:
257
+ logger.error(f"Error installing server: {e}")
258
+ emit_info(f"[red]Installation failed: {e}[/red]", message_group=group_id)
259
+ return False
@@ -70,9 +70,9 @@ class ModelNameCompleter(Completer):
70
70
 
71
71
 
72
72
  def update_model_in_input(text: str) -> Optional[str]:
73
- # If input starts with /model and a model name, set model and strip it out
73
+ # If input starts with /model or /m and a model name, set model and strip it out
74
74
  content = text.strip()
75
-
75
+
76
76
  # Check for /model command
77
77
  if content.startswith("/model"):
78
78
  rest = content[6:].strip() # Remove '/model'
@@ -82,13 +82,30 @@ def update_model_in_input(text: str) -> Optional[str]:
82
82
  # Remove /model from the input
83
83
  idx = text.find("/model" + model)
84
84
  if idx != -1:
85
- new_text = (text[:idx] + text[idx + len("/model" + model) :]).strip()
85
+ new_text = (
86
+ text[:idx] + text[idx + len("/model" + model) :]
87
+ ).strip()
88
+ return new_text
89
+
90
+ # Check for /m command
91
+ elif content.startswith("/m "):
92
+ rest = content[3:].strip() # Remove '/m '
93
+ for model in load_model_names():
94
+ if rest == model:
95
+ set_active_model(model)
96
+ # Remove /m from the input
97
+ idx = text.find("/m " + model)
98
+ if idx != -1:
99
+ new_text = (text[:idx] + text[idx + len("/m " + model) :]).strip()
86
100
  return new_text
101
+
87
102
  return None
88
103
 
89
104
 
90
105
  async def get_input_with_model_completion(
91
- prompt_str: str = ">>> ", trigger: str = "/model", history_file: Optional[str] = None
106
+ prompt_str: str = ">>> ",
107
+ trigger: str = "/model",
108
+ history_file: Optional[str] = None,
92
109
  ) -> str:
93
110
  history = FileHistory(os.path.expanduser(history_file)) if history_file else None
94
111
  session = PromptSession(
@@ -136,8 +136,15 @@ class CDCompleter(Completer):
136
136
 
137
137
 
138
138
  def get_prompt_with_active_model(base: str = ">>> "):
139
+ from code_puppy.agents.agent_manager import get_current_agent_config
140
+
139
141
  puppy = get_puppy_name()
140
142
  model = get_active_model() or "(default)"
143
+
144
+ # Get current agent information
145
+ current_agent = get_current_agent_config()
146
+ agent_display = current_agent.display_name if current_agent else "code-puppy"
147
+
141
148
  cwd = os.getcwd()
142
149
  home = os.path.expanduser("~")
143
150
  if cwd.startswith(home):
@@ -149,6 +156,7 @@ def get_prompt_with_active_model(base: str = ">>> "):
149
156
  ("bold", "🐶 "),
150
157
  ("class:puppy", f"{puppy}"),
151
158
  ("", " "),
159
+ ("class:agent", f"[{agent_display}] "),
152
160
  ("class:model", "[" + str(model) + "] "),
153
161
  ("class:cwd", "(" + str(cwd_display) + ") "),
154
162
  ("class:arrow", str(base)),
@@ -213,6 +221,7 @@ async def get_input_with_combined_completion(
213
221
  # tagging tokens in `FormattedText`. See prompt_toolkit docs.
214
222
  "puppy": "bold magenta",
215
223
  "owner": "bold white",
224
+ "agent": "bold blue",
216
225
  "model": "bold cyan",
217
226
  "cwd": "bold green",
218
227
  "arrow": "bold yellow",
code_puppy/main.py CHANGED
@@ -11,7 +11,7 @@ from rich.markdown import CodeBlock, Markdown
11
11
  from rich.syntax import Syntax
12
12
  from rich.text import Text
13
13
 
14
- from code_puppy import __version__, callbacks, plugins, state_management
14
+ from code_puppy import __version__, callbacks, plugins
15
15
  from code_puppy.agent import get_custom_usage_limits
16
16
  from code_puppy.agents.runtime_manager import get_runtime_agent_manager
17
17
  from code_puppy.command_line.prompt_toolkit_completion import (
@@ -29,7 +29,7 @@ from code_puppy.message_history_processor import (
29
29
  message_history_accumulator,
30
30
  prune_interrupted_tool_calls,
31
31
  )
32
- from code_puppy.state_management import is_tui_mode, set_tui_mode, set_message_history
32
+ from code_puppy.state_management import is_tui_mode, set_message_history, set_tui_mode
33
33
  from code_puppy.tools.common import console
34
34
  from code_puppy.version_checker import default_version_mismatch_behavior
35
35
 
@@ -263,8 +263,8 @@ async def interactive_mode(message_renderer, initial_command: str = None) -> Non
263
263
  from code_puppy.messaging import emit_warning
264
264
 
265
265
  emit_warning(f"MOTD error: {e}")
266
- from code_puppy.messaging import emit_info
267
266
  from code_puppy.agents.runtime_manager import get_runtime_agent_manager
267
+ from code_puppy.messaging import emit_info
268
268
 
269
269
  emit_info("[bold cyan]Initializing agent...[/bold cyan]")
270
270
  # Initialize the runtime agent manager
@@ -352,8 +352,8 @@ async def interactive_mode(message_renderer, initial_command: str = None) -> Non
352
352
  emit_warning("Falling back to basic input without tab completion")
353
353
 
354
354
  while True:
355
- from code_puppy.messaging import emit_info
356
355
  from code_puppy.agents.agent_manager import get_current_agent_config
356
+ from code_puppy.messaging import emit_info
357
357
 
358
358
  # Get the custom prompt from the current agent, or use default
359
359
  current_agent = get_current_agent_config()
@@ -426,15 +426,17 @@ async def interactive_mode(message_renderer, initial_command: str = None) -> Non
426
426
 
427
427
  # Create a task that mimics TUI behavior - avoid signal handler conflicts
428
428
  current_task = None
429
- signal_handled = False # Prevent multiple signal handler calls (reset per task)
430
-
429
+ signal_handled = (
430
+ False # Prevent multiple signal handler calls (reset per task)
431
+ )
432
+
431
433
  async def run_task():
432
434
  # Use the simpler run() method instead of run_with_mcp() to avoid signal handler
433
435
  agent = agent_manager.get_agent()
434
436
  async with agent:
435
437
  return await agent.run(
436
438
  task,
437
- message_history=get_message_history(),
439
+ message_history=get_message_history(),
438
440
  usage_limits=get_custom_usage_limits(),
439
441
  )
440
442
 
@@ -444,27 +446,29 @@ async def interactive_mode(message_renderer, initial_command: str = None) -> Non
444
446
  if signal_handled:
445
447
  return
446
448
  signal_handled = True
447
-
448
- from code_puppy.tools.command_runner import kill_all_running_shell_processes
449
-
449
+
450
+ from code_puppy.tools.command_runner import (
451
+ kill_all_running_shell_processes,
452
+ )
453
+
450
454
  killed = kill_all_running_shell_processes()
451
455
  if killed:
452
456
  emit_warning(f"🔥 Cancelled {killed} running shell process(es)")
453
457
  # Don't cancel the agent task - let it continue processing
454
458
  # Shell processes killed, but agent continues running
455
459
  else:
456
- # Only cancel the agent task if NO processes were killed
460
+ # Only cancel the agent task if NO processes were killed
457
461
  if current_task and not current_task.done():
458
462
  current_task.cancel()
459
463
  emit_warning("⚠️ Processing cancelled by user")
460
464
 
461
465
  # Set up proper signal handling to override asyncio's default behavior
462
466
  import signal
463
-
467
+
464
468
  def signal_handler(sig, frame):
465
469
  """Handle Ctrl+C by killing processes and cancelling the current task"""
466
470
  handle_keyboard_interrupt()
467
-
471
+
468
472
  # Replace asyncio's SIGINT handler with our own
469
473
  original_handler = signal.signal(signal.SIGINT, signal_handler)
470
474
 
@@ -483,7 +487,7 @@ async def interactive_mode(message_renderer, initial_command: str = None) -> Non
483
487
  result = None
484
488
  finally:
485
489
  # Restore original signal handler
486
- if 'original_handler' in locals():
490
+ if "original_handler" in locals():
487
491
  signal.signal(signal.SIGINT, original_handler)
488
492
  set_message_history(
489
493
  prune_interrupted_tool_calls(get_message_history())
@@ -559,12 +563,12 @@ async def execute_single_prompt(prompt: str, message_renderer) -> None:
559
563
  try:
560
564
  # Get agent through runtime manager and use its run_with_mcp method
561
565
  agent_manager = get_runtime_agent_manager()
562
-
566
+
563
567
  from code_puppy.messaging.spinner import ConsoleSpinner
568
+
564
569
  with ConsoleSpinner(console=message_renderer.console):
565
570
  response = await agent_manager.run_with_mcp(
566
- prompt,
567
- usage_limits=get_custom_usage_limits()
571
+ prompt, usage_limits=get_custom_usage_limits()
568
572
  )
569
573
 
570
574
  agent_response = response.output
@@ -574,9 +578,11 @@ async def execute_single_prompt(prompt: str, message_renderer) -> None:
574
578
 
575
579
  except asyncio.CancelledError:
576
580
  from code_puppy.messaging import emit_warning
581
+
577
582
  emit_warning("Execution cancelled by user")
578
583
  except Exception as e:
579
584
  from code_puppy.messaging import emit_error
585
+
580
586
  emit_error(f"Error executing prompt: {str(e)}")
581
587
 
582
588
 
@@ -1,23 +1,49 @@
1
- """MCP (Model Context Protocol) management system for Code Puppy."""
1
+ """MCP (Model Context Protocol) management system for Code Puppy.
2
2
 
3
+ Note: Be careful not to create circular imports with config_wizard.py.
4
+ config_wizard.py imports ServerConfig and get_mcp_manager directly from
5
+ .manager to avoid circular dependencies with this package __init__.py
6
+ """
7
+
8
+ from .circuit_breaker import CircuitBreaker, CircuitOpenError, CircuitState
9
+ from .config_wizard import MCPConfigWizard, run_add_wizard
10
+ from .dashboard import MCPDashboard
11
+ from .error_isolation import (
12
+ ErrorCategory,
13
+ ErrorStats,
14
+ MCPErrorIsolator,
15
+ QuarantinedServerError,
16
+ get_error_isolator,
17
+ )
3
18
  from .managed_server import ManagedMCPServer, ServerConfig, ServerState
4
- from .status_tracker import ServerStatusTracker, Event
5
19
  from .manager import MCPManager, ServerInfo, get_mcp_manager
6
20
  from .registry import ServerRegistry
7
- from .error_isolation import MCPErrorIsolator, ErrorStats, ErrorCategory, QuarantinedServerError, get_error_isolator
8
- from .circuit_breaker import CircuitBreaker, CircuitState, CircuitOpenError
9
21
  from .retry_manager import RetryManager, RetryStats, get_retry_manager, retry_mcp_call
10
- from .dashboard import MCPDashboard
11
- from .config_wizard import MCPConfigWizard, run_add_wizard
22
+ from .status_tracker import Event, ServerStatusTracker
12
23
 
13
24
  __all__ = [
14
- 'ManagedMCPServer', 'ServerConfig', 'ServerState',
15
- 'ServerStatusTracker', 'Event',
16
- 'MCPManager', 'ServerInfo', 'get_mcp_manager',
17
- 'ServerRegistry',
18
- 'MCPErrorIsolator', 'ErrorStats', 'ErrorCategory', 'QuarantinedServerError', 'get_error_isolator',
19
- 'CircuitBreaker', 'CircuitState', 'CircuitOpenError',
20
- 'RetryManager', 'RetryStats', 'get_retry_manager', 'retry_mcp_call',
21
- 'MCPDashboard',
22
- 'MCPConfigWizard', 'run_add_wizard'
23
- ]
25
+ "ManagedMCPServer",
26
+ "ServerConfig",
27
+ "ServerState",
28
+ "ServerStatusTracker",
29
+ "Event",
30
+ "MCPManager",
31
+ "ServerInfo",
32
+ "get_mcp_manager",
33
+ "ServerRegistry",
34
+ "MCPErrorIsolator",
35
+ "ErrorStats",
36
+ "ErrorCategory",
37
+ "QuarantinedServerError",
38
+ "get_error_isolator",
39
+ "CircuitBreaker",
40
+ "CircuitState",
41
+ "CircuitOpenError",
42
+ "RetryManager",
43
+ "RetryStats",
44
+ "get_retry_manager",
45
+ "retry_mcp_call",
46
+ "MCPDashboard",
47
+ "MCPConfigWizard",
48
+ "run_add_wizard",
49
+ ]