code-puppy 0.0.135__py3-none-any.whl → 0.0.137__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/config.py +5 -5
- 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 -93
- 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.135.dist-info → code_puppy-0.0.137.dist-info}/METADATA +1 -1
- {code_puppy-0.0.135.dist-info → code_puppy-0.0.137.dist-info}/RECORD +60 -42
- code_puppy/command_line/mcp_commands.py +0 -1789
- {code_puppy-0.0.135.data → code_puppy-0.0.137.data}/data/code_puppy/models.json +0 -0
- {code_puppy-0.0.135.dist-info → code_puppy-0.0.137.dist-info}/WHEEL +0 -0
- {code_puppy-0.0.135.dist-info → code_puppy-0.0.137.dist-info}/entry_points.txt +0 -0
- {code_puppy-0.0.135.dist-info → code_puppy-0.0.137.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP Status Command - Shows detailed status for MCP servers.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from typing import List, Optional
|
|
8
|
+
|
|
9
|
+
from rich.panel import Panel
|
|
10
|
+
|
|
11
|
+
from code_puppy.mcp.managed_server import ServerState
|
|
12
|
+
from code_puppy.messaging import emit_info
|
|
13
|
+
|
|
14
|
+
from .base import MCPCommandBase
|
|
15
|
+
from .list_command import ListCommand
|
|
16
|
+
from .utils import (
|
|
17
|
+
find_server_id_by_name,
|
|
18
|
+
format_state_indicator,
|
|
19
|
+
format_uptime,
|
|
20
|
+
suggest_similar_servers,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
# Configure logging
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class StatusCommand(MCPCommandBase):
|
|
28
|
+
"""
|
|
29
|
+
Command handler for showing MCP server status.
|
|
30
|
+
|
|
31
|
+
Shows detailed status for a specific server or brief status for all servers.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def execute(self, args: List[str], group_id: Optional[str] = None) -> None:
|
|
35
|
+
"""
|
|
36
|
+
Show detailed status for a specific server or all servers.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
args: Command arguments, expects [server_name] (optional)
|
|
40
|
+
group_id: Optional message group ID for grouping related messages
|
|
41
|
+
"""
|
|
42
|
+
if group_id is None:
|
|
43
|
+
group_id = self.generate_group_id()
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
if args:
|
|
47
|
+
# Show detailed status for specific server
|
|
48
|
+
server_name = args[0]
|
|
49
|
+
server_id = find_server_id_by_name(self.manager, server_name)
|
|
50
|
+
|
|
51
|
+
if not server_id:
|
|
52
|
+
emit_info(
|
|
53
|
+
f"Server '{server_name}' not found", message_group=group_id
|
|
54
|
+
)
|
|
55
|
+
suggest_similar_servers(
|
|
56
|
+
self.manager, server_name, group_id=group_id
|
|
57
|
+
)
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
self._show_detailed_server_status(server_id, server_name, group_id)
|
|
61
|
+
else:
|
|
62
|
+
# Show brief status for all servers
|
|
63
|
+
list_command = ListCommand()
|
|
64
|
+
list_command.execute([], group_id=group_id)
|
|
65
|
+
|
|
66
|
+
except Exception as e:
|
|
67
|
+
logger.error(f"Error showing server status: {e}")
|
|
68
|
+
emit_info(f"Failed to get server status: {e}", message_group=group_id)
|
|
69
|
+
|
|
70
|
+
def _show_detailed_server_status(
|
|
71
|
+
self, server_id: str, server_name: str, group_id: Optional[str] = None
|
|
72
|
+
) -> None:
|
|
73
|
+
"""
|
|
74
|
+
Show comprehensive status information for a specific server.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
server_id: ID of the server
|
|
78
|
+
server_name: Name of the server
|
|
79
|
+
group_id: Optional message group ID
|
|
80
|
+
"""
|
|
81
|
+
if group_id is None:
|
|
82
|
+
group_id = self.generate_group_id()
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
status = self.manager.get_server_status(server_id)
|
|
86
|
+
|
|
87
|
+
if not status.get("exists", True):
|
|
88
|
+
emit_info(
|
|
89
|
+
f"Server '{server_name}' not found or not accessible",
|
|
90
|
+
message_group=group_id,
|
|
91
|
+
)
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
# Create detailed status panel
|
|
95
|
+
status_lines = []
|
|
96
|
+
|
|
97
|
+
# Basic information
|
|
98
|
+
status_lines.append(f"[bold]Server:[/bold] {server_name}")
|
|
99
|
+
status_lines.append(f"[bold]ID:[/bold] {server_id}")
|
|
100
|
+
status_lines.append(
|
|
101
|
+
f"[bold]Type:[/bold] {status.get('type', 'unknown').upper()}"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# State and status
|
|
105
|
+
state = status.get("state", "unknown")
|
|
106
|
+
state_display = format_state_indicator(
|
|
107
|
+
ServerState(state)
|
|
108
|
+
if state in [s.value for s in ServerState]
|
|
109
|
+
else ServerState.STOPPED
|
|
110
|
+
)
|
|
111
|
+
status_lines.append(f"[bold]State:[/bold] {state_display}")
|
|
112
|
+
|
|
113
|
+
enabled = status.get("enabled", False)
|
|
114
|
+
status_lines.append(
|
|
115
|
+
f"[bold]Enabled:[/bold] {'✓ Yes' if enabled else '✗ No'}"
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# Check async lifecycle manager status if available
|
|
119
|
+
try:
|
|
120
|
+
from code_puppy.mcp.async_lifecycle import get_lifecycle_manager
|
|
121
|
+
|
|
122
|
+
lifecycle_mgr = get_lifecycle_manager()
|
|
123
|
+
if lifecycle_mgr.is_running(server_id):
|
|
124
|
+
status_lines.append(
|
|
125
|
+
"[bold]Process:[/bold] [green]✓ Active (subprocess/connection running)[/green]"
|
|
126
|
+
)
|
|
127
|
+
else:
|
|
128
|
+
status_lines.append("[bold]Process:[/bold] [dim]Not active[/dim]")
|
|
129
|
+
except Exception:
|
|
130
|
+
pass # Lifecycle manager not available
|
|
131
|
+
|
|
132
|
+
quarantined = status.get("quarantined", False)
|
|
133
|
+
if quarantined:
|
|
134
|
+
status_lines.append("[bold]Quarantined:[/bold] [yellow]⚠ Yes[/yellow]")
|
|
135
|
+
|
|
136
|
+
# Timing information
|
|
137
|
+
uptime = status.get("tracker_uptime")
|
|
138
|
+
if uptime:
|
|
139
|
+
uptime_str = format_uptime(
|
|
140
|
+
uptime.total_seconds()
|
|
141
|
+
if hasattr(uptime, "total_seconds")
|
|
142
|
+
else uptime
|
|
143
|
+
)
|
|
144
|
+
status_lines.append(f"[bold]Uptime:[/bold] {uptime_str}")
|
|
145
|
+
|
|
146
|
+
# Error information
|
|
147
|
+
error_msg = status.get("error_message")
|
|
148
|
+
if error_msg:
|
|
149
|
+
status_lines.append(f"[bold]Error:[/bold] [red]{error_msg}[/red]")
|
|
150
|
+
|
|
151
|
+
# Event information
|
|
152
|
+
event_count = status.get("recent_events_count", 0)
|
|
153
|
+
status_lines.append(f"[bold]Recent Events:[/bold] {event_count}")
|
|
154
|
+
|
|
155
|
+
# Metadata
|
|
156
|
+
metadata = status.get("tracker_metadata", {})
|
|
157
|
+
if metadata:
|
|
158
|
+
status_lines.append(f"[bold]Metadata:[/bold] {len(metadata)} keys")
|
|
159
|
+
|
|
160
|
+
# Create and show the panel
|
|
161
|
+
panel_content = "\n".join(status_lines)
|
|
162
|
+
panel = Panel(
|
|
163
|
+
panel_content, title=f"🔌 {server_name} Status", border_style="cyan"
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
emit_info(panel, message_group=group_id)
|
|
167
|
+
|
|
168
|
+
# Show recent events if available
|
|
169
|
+
recent_events = status.get("recent_events", [])
|
|
170
|
+
if recent_events:
|
|
171
|
+
emit_info("\n📋 Recent Events:", message_group=group_id)
|
|
172
|
+
for event in recent_events[-5:]: # Show last 5 events
|
|
173
|
+
timestamp = datetime.fromisoformat(event["timestamp"])
|
|
174
|
+
time_str = timestamp.strftime("%H:%M:%S")
|
|
175
|
+
emit_info(
|
|
176
|
+
f" {time_str}: {event['message']}", message_group=group_id
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
except Exception as e:
|
|
180
|
+
logger.error(
|
|
181
|
+
f"Error getting detailed status for server '{server_name}': {e}"
|
|
182
|
+
)
|
|
183
|
+
emit_info(
|
|
184
|
+
f"[red]Error getting server status: {e}[/red]", message_group=group_id
|
|
185
|
+
)
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP Stop All Command - Stops all running MCP servers.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import time
|
|
7
|
+
from typing import List, Optional
|
|
8
|
+
|
|
9
|
+
from code_puppy.mcp.managed_server import ServerState
|
|
10
|
+
from code_puppy.messaging import emit_info
|
|
11
|
+
|
|
12
|
+
from .base import MCPCommandBase
|
|
13
|
+
|
|
14
|
+
# Configure logging
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class StopAllCommand(MCPCommandBase):
|
|
19
|
+
"""
|
|
20
|
+
Command handler for stopping all MCP servers.
|
|
21
|
+
|
|
22
|
+
Stops all running MCP servers and provides a summary of results.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def execute(self, args: List[str], group_id: Optional[str] = None) -> None:
|
|
26
|
+
"""
|
|
27
|
+
Stop all running MCP servers.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
args: Command arguments (unused)
|
|
31
|
+
group_id: Optional message group ID for grouping related messages
|
|
32
|
+
"""
|
|
33
|
+
if group_id is None:
|
|
34
|
+
group_id = self.generate_group_id()
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
servers = self.manager.list_servers()
|
|
38
|
+
|
|
39
|
+
if not servers:
|
|
40
|
+
emit_info("No servers registered", message_group=group_id)
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
stopped_count = 0
|
|
44
|
+
failed_count = 0
|
|
45
|
+
|
|
46
|
+
# Count running servers
|
|
47
|
+
running_servers = [s for s in servers if s.state == ServerState.RUNNING]
|
|
48
|
+
|
|
49
|
+
if not running_servers:
|
|
50
|
+
emit_info("No servers are currently running", message_group=group_id)
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
emit_info(
|
|
54
|
+
f"Stopping {len(running_servers)} running server(s)...",
|
|
55
|
+
message_group=group_id,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
for server_info in running_servers:
|
|
59
|
+
server_id = server_info.id
|
|
60
|
+
server_name = server_info.name
|
|
61
|
+
|
|
62
|
+
# Try to stop the server
|
|
63
|
+
success = self.manager.stop_server_sync(server_id)
|
|
64
|
+
|
|
65
|
+
if success:
|
|
66
|
+
stopped_count += 1
|
|
67
|
+
emit_info(f" ✓ Stopped: {server_name}", message_group=group_id)
|
|
68
|
+
else:
|
|
69
|
+
failed_count += 1
|
|
70
|
+
emit_info(f" ✗ Failed: {server_name}", message_group=group_id)
|
|
71
|
+
|
|
72
|
+
# Summary
|
|
73
|
+
emit_info("", message_group=group_id)
|
|
74
|
+
if stopped_count > 0:
|
|
75
|
+
emit_info(f"Stopped {stopped_count} server(s)", message_group=group_id)
|
|
76
|
+
if failed_count > 0:
|
|
77
|
+
emit_info(
|
|
78
|
+
f"Failed to stop {failed_count} server(s)", message_group=group_id
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# Reload agent if any servers were stopped
|
|
82
|
+
if stopped_count > 0:
|
|
83
|
+
# Give async tasks a moment to complete before reloading agent
|
|
84
|
+
try:
|
|
85
|
+
import asyncio
|
|
86
|
+
|
|
87
|
+
asyncio.get_running_loop() # Check if in async context
|
|
88
|
+
# If we're in async context, wait a bit for servers to stop
|
|
89
|
+
time.sleep(0.5) # Small delay to let async tasks progress
|
|
90
|
+
except RuntimeError:
|
|
91
|
+
pass # No async loop, servers will stop when needed
|
|
92
|
+
|
|
93
|
+
try:
|
|
94
|
+
from code_puppy.agents.runtime_manager import (
|
|
95
|
+
get_runtime_agent_manager,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
manager = get_runtime_agent_manager()
|
|
99
|
+
manager.reload_agent()
|
|
100
|
+
emit_info(
|
|
101
|
+
"[dim]Agent reloaded with updated servers[/dim]",
|
|
102
|
+
message_group=group_id,
|
|
103
|
+
)
|
|
104
|
+
except Exception as e:
|
|
105
|
+
logger.warning(f"Could not reload agent: {e}")
|
|
106
|
+
|
|
107
|
+
except Exception as e:
|
|
108
|
+
logger.error(f"Error stopping all servers: {e}")
|
|
109
|
+
emit_info(f"Failed to stop servers: {e}", message_group=group_id)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP Stop Command - Stops a specific MCP server.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import List, Optional
|
|
7
|
+
|
|
8
|
+
from code_puppy.messaging import emit_info
|
|
9
|
+
|
|
10
|
+
from .base import MCPCommandBase
|
|
11
|
+
from .utils import find_server_id_by_name, suggest_similar_servers
|
|
12
|
+
|
|
13
|
+
# Configure logging
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class StopCommand(MCPCommandBase):
|
|
18
|
+
"""
|
|
19
|
+
Command handler for stopping MCP servers.
|
|
20
|
+
|
|
21
|
+
Stops a specific MCP server by name and reloads the agent.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def execute(self, args: List[str], group_id: Optional[str] = None) -> None:
|
|
25
|
+
"""
|
|
26
|
+
Stop a specific MCP server.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
args: Command arguments, expects [server_name]
|
|
30
|
+
group_id: Optional message group ID for grouping related messages
|
|
31
|
+
"""
|
|
32
|
+
if group_id is None:
|
|
33
|
+
group_id = self.generate_group_id()
|
|
34
|
+
|
|
35
|
+
if not args:
|
|
36
|
+
emit_info(
|
|
37
|
+
"[yellow]Usage: /mcp stop <server_name>[/yellow]",
|
|
38
|
+
message_group=group_id,
|
|
39
|
+
)
|
|
40
|
+
return
|
|
41
|
+
|
|
42
|
+
server_name = args[0]
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
# Find server by name
|
|
46
|
+
server_id = find_server_id_by_name(self.manager, server_name)
|
|
47
|
+
if not server_id:
|
|
48
|
+
emit_info(f"Server '{server_name}' not found", message_group=group_id)
|
|
49
|
+
suggest_similar_servers(self.manager, server_name, group_id=group_id)
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
# Stop the server (disable and stop process)
|
|
53
|
+
success = self.manager.stop_server_sync(server_id)
|
|
54
|
+
|
|
55
|
+
if success:
|
|
56
|
+
emit_info(f"✓ Stopped server: {server_name}", message_group=group_id)
|
|
57
|
+
|
|
58
|
+
# Reload the agent to remove the disabled server
|
|
59
|
+
try:
|
|
60
|
+
from code_puppy.agents.runtime_manager import (
|
|
61
|
+
get_runtime_agent_manager,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
manager = get_runtime_agent_manager()
|
|
65
|
+
manager.reload_agent()
|
|
66
|
+
emit_info(
|
|
67
|
+
"[dim]Agent reloaded with updated servers[/dim]",
|
|
68
|
+
message_group=group_id,
|
|
69
|
+
)
|
|
70
|
+
except Exception as e:
|
|
71
|
+
logger.warning(f"Could not reload agent: {e}")
|
|
72
|
+
else:
|
|
73
|
+
emit_info(
|
|
74
|
+
f"✗ Failed to stop server: {server_name}", message_group=group_id
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
except Exception as e:
|
|
78
|
+
logger.error(f"Error stopping server '{server_name}': {e}")
|
|
79
|
+
emit_info(f"[red]Failed to stop server: {e}[/red]", message_group=group_id)
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP Test Command - Tests connectivity to a specific MCP server.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import List, Optional
|
|
7
|
+
|
|
8
|
+
from code_puppy.messaging import emit_info
|
|
9
|
+
|
|
10
|
+
from .base import MCPCommandBase
|
|
11
|
+
from .utils import find_server_id_by_name, suggest_similar_servers
|
|
12
|
+
|
|
13
|
+
# Configure logging
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class TestCommand(MCPCommandBase):
|
|
18
|
+
"""
|
|
19
|
+
Command handler for testing MCP server connectivity.
|
|
20
|
+
|
|
21
|
+
Tests connectivity and basic functionality of a specific MCP server.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def execute(self, args: List[str], group_id: Optional[str] = None) -> None:
|
|
25
|
+
"""
|
|
26
|
+
Test connectivity to a specific MCP server.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
args: Command arguments, expects [server_name]
|
|
30
|
+
group_id: Optional message group ID for grouping related messages
|
|
31
|
+
"""
|
|
32
|
+
if group_id is None:
|
|
33
|
+
group_id = self.generate_group_id()
|
|
34
|
+
|
|
35
|
+
if not args:
|
|
36
|
+
emit_info("Usage: /mcp test <server_name>", message_group=group_id)
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
server_name = args[0]
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
# Find server by name
|
|
43
|
+
server_id = find_server_id_by_name(self.manager, server_name)
|
|
44
|
+
if not server_id:
|
|
45
|
+
emit_info(f"Server '{server_name}' not found", message_group=group_id)
|
|
46
|
+
suggest_similar_servers(self.manager, server_name, group_id=group_id)
|
|
47
|
+
return
|
|
48
|
+
|
|
49
|
+
# Get managed server
|
|
50
|
+
managed_server = self.manager.get_server(server_id)
|
|
51
|
+
if not managed_server:
|
|
52
|
+
emit_info(
|
|
53
|
+
f"Server '{server_name}' not accessible", message_group=group_id
|
|
54
|
+
)
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
emit_info(
|
|
58
|
+
f"🔍 Testing connectivity to server: {server_name}",
|
|
59
|
+
message_group=group_id,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Basic connectivity test - try to get the pydantic server
|
|
63
|
+
try:
|
|
64
|
+
managed_server.get_pydantic_server() # Test server instantiation
|
|
65
|
+
emit_info(
|
|
66
|
+
"✓ Server instance created successfully", message_group=group_id
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# Try to get server info if available
|
|
70
|
+
emit_info(
|
|
71
|
+
f" • Server type: {managed_server.config.type}",
|
|
72
|
+
message_group=group_id,
|
|
73
|
+
)
|
|
74
|
+
emit_info(
|
|
75
|
+
f" • Server enabled: {managed_server.is_enabled()}",
|
|
76
|
+
message_group=group_id,
|
|
77
|
+
)
|
|
78
|
+
emit_info(
|
|
79
|
+
f" • Server quarantined: {managed_server.is_quarantined()}",
|
|
80
|
+
message_group=group_id,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
if not managed_server.is_enabled():
|
|
84
|
+
emit_info(
|
|
85
|
+
" • Server is disabled - enable it with '/mcp start'",
|
|
86
|
+
message_group=group_id,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
if managed_server.is_quarantined():
|
|
90
|
+
emit_info(
|
|
91
|
+
" • Server is quarantined - may have recent errors",
|
|
92
|
+
message_group=group_id,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
emit_info(
|
|
96
|
+
f"✓ Connectivity test passed for: {server_name}",
|
|
97
|
+
message_group=group_id,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
except Exception as test_error:
|
|
101
|
+
emit_info(
|
|
102
|
+
f"✗ Connectivity test failed: {test_error}", message_group=group_id
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
except Exception as e:
|
|
106
|
+
logger.error(f"Error testing server '{server_name}': {e}")
|
|
107
|
+
emit_info(f"[red]Error testing server: {e}[/red]", message_group=group_id)
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP Command Utilities - Shared helper functions for MCP command handlers.
|
|
3
|
+
|
|
4
|
+
Provides common utility functions used across multiple MCP command modules.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
from rich.text import Text
|
|
10
|
+
|
|
11
|
+
from code_puppy.mcp.managed_server import ServerState
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def format_state_indicator(state: ServerState) -> Text:
|
|
15
|
+
"""
|
|
16
|
+
Format a server state with appropriate color and icon.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
state: Server state to format
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
Rich Text object with colored state indicator
|
|
23
|
+
"""
|
|
24
|
+
state_map = {
|
|
25
|
+
ServerState.RUNNING: ("✓ Run", "green"),
|
|
26
|
+
ServerState.STOPPED: ("✗ Stop", "red"),
|
|
27
|
+
ServerState.STARTING: ("↗ Start", "yellow"),
|
|
28
|
+
ServerState.STOPPING: ("↙ Stop", "yellow"),
|
|
29
|
+
ServerState.ERROR: ("⚠ Err", "red"),
|
|
30
|
+
ServerState.QUARANTINED: ("⏸ Quar", "yellow"),
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
display, color = state_map.get(state, ("? Unk", "dim"))
|
|
34
|
+
return Text(display, style=color)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def format_uptime(uptime_seconds: Optional[float]) -> str:
|
|
38
|
+
"""
|
|
39
|
+
Format uptime in a human-readable format.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
uptime_seconds: Uptime in seconds, or None
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
Formatted uptime string
|
|
46
|
+
"""
|
|
47
|
+
if uptime_seconds is None or uptime_seconds <= 0:
|
|
48
|
+
return "-"
|
|
49
|
+
|
|
50
|
+
# Convert to readable format
|
|
51
|
+
if uptime_seconds < 60:
|
|
52
|
+
return f"{int(uptime_seconds)}s"
|
|
53
|
+
elif uptime_seconds < 3600:
|
|
54
|
+
minutes = int(uptime_seconds // 60)
|
|
55
|
+
seconds = int(uptime_seconds % 60)
|
|
56
|
+
return f"{minutes}m {seconds}s"
|
|
57
|
+
else:
|
|
58
|
+
hours = int(uptime_seconds // 3600)
|
|
59
|
+
minutes = int((uptime_seconds % 3600) // 60)
|
|
60
|
+
return f"{hours}h {minutes}m"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def find_server_id_by_name(manager, server_name: str) -> Optional[str]:
|
|
64
|
+
"""
|
|
65
|
+
Find a server ID by its name.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
manager: MCP manager instance
|
|
69
|
+
server_name: Name of the server to find
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Server ID if found, None otherwise
|
|
73
|
+
"""
|
|
74
|
+
import logging
|
|
75
|
+
|
|
76
|
+
logger = logging.getLogger(__name__)
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
servers = manager.list_servers()
|
|
80
|
+
for server in servers:
|
|
81
|
+
if server.name.lower() == server_name.lower():
|
|
82
|
+
return server.id
|
|
83
|
+
return None
|
|
84
|
+
except Exception as e:
|
|
85
|
+
logger.error(f"Error finding server by name '{server_name}': {e}")
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def suggest_similar_servers(
|
|
90
|
+
manager, server_name: str, group_id: Optional[str] = None
|
|
91
|
+
) -> None:
|
|
92
|
+
"""
|
|
93
|
+
Suggest similar server names when a server is not found.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
manager: MCP manager instance
|
|
97
|
+
server_name: The server name that was not found
|
|
98
|
+
group_id: Optional message group ID for grouping related messages
|
|
99
|
+
"""
|
|
100
|
+
import logging
|
|
101
|
+
|
|
102
|
+
from code_puppy.messaging import emit_info
|
|
103
|
+
|
|
104
|
+
logger = logging.getLogger(__name__)
|
|
105
|
+
|
|
106
|
+
try:
|
|
107
|
+
servers = manager.list_servers()
|
|
108
|
+
if not servers:
|
|
109
|
+
emit_info("No servers are registered", message_group=group_id)
|
|
110
|
+
return
|
|
111
|
+
|
|
112
|
+
# Simple suggestion based on partial matching
|
|
113
|
+
suggestions = []
|
|
114
|
+
server_name_lower = server_name.lower()
|
|
115
|
+
|
|
116
|
+
for server in servers:
|
|
117
|
+
if server_name_lower in server.name.lower():
|
|
118
|
+
suggestions.append(server.name)
|
|
119
|
+
|
|
120
|
+
if suggestions:
|
|
121
|
+
emit_info(f"Did you mean: {', '.join(suggestions)}", message_group=group_id)
|
|
122
|
+
else:
|
|
123
|
+
server_names = [s.name for s in servers]
|
|
124
|
+
emit_info(
|
|
125
|
+
f"Available servers: {', '.join(server_names)}", message_group=group_id
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
except Exception as e:
|
|
129
|
+
logger.error(f"Error suggesting similar servers: {e}")
|