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
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP Command Handler - Main router for MCP server management commands.
|
|
3
|
+
|
|
4
|
+
This module provides the MCPCommandHandler class that routes MCP commands
|
|
5
|
+
to their respective command modules.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
import shlex
|
|
10
|
+
|
|
11
|
+
from code_puppy.messaging import emit_info
|
|
12
|
+
|
|
13
|
+
from .add_command import AddCommand
|
|
14
|
+
from .base import MCPCommandBase
|
|
15
|
+
from .help_command import HelpCommand
|
|
16
|
+
from .install_command import InstallCommand
|
|
17
|
+
|
|
18
|
+
# Import all command modules
|
|
19
|
+
from .list_command import ListCommand
|
|
20
|
+
from .logs_command import LogsCommand
|
|
21
|
+
from .remove_command import RemoveCommand
|
|
22
|
+
from .restart_command import RestartCommand
|
|
23
|
+
from .search_command import SearchCommand
|
|
24
|
+
from .start_all_command import StartAllCommand
|
|
25
|
+
from .start_command import StartCommand
|
|
26
|
+
from .status_command import StatusCommand
|
|
27
|
+
from .stop_all_command import StopAllCommand
|
|
28
|
+
from .stop_command import StopCommand
|
|
29
|
+
from .test_command import TestCommand
|
|
30
|
+
|
|
31
|
+
# Configure logging
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class MCPCommandHandler(MCPCommandBase):
|
|
36
|
+
"""
|
|
37
|
+
Main command handler for MCP server management operations.
|
|
38
|
+
|
|
39
|
+
Routes MCP commands to their respective command modules.
|
|
40
|
+
Each command is implemented in its own module for better maintainability.
|
|
41
|
+
|
|
42
|
+
Example usage:
|
|
43
|
+
handler = MCPCommandHandler()
|
|
44
|
+
handler.handle_mcp_command("/mcp list")
|
|
45
|
+
handler.handle_mcp_command("/mcp start filesystem")
|
|
46
|
+
handler.handle_mcp_command("/mcp status filesystem")
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(self):
|
|
50
|
+
"""Initialize the MCP command handler."""
|
|
51
|
+
super().__init__()
|
|
52
|
+
|
|
53
|
+
# Initialize command handlers
|
|
54
|
+
self._commands = {
|
|
55
|
+
"list": ListCommand(),
|
|
56
|
+
"start": StartCommand(),
|
|
57
|
+
"start-all": StartAllCommand(),
|
|
58
|
+
"stop": StopCommand(),
|
|
59
|
+
"stop-all": StopAllCommand(),
|
|
60
|
+
"restart": RestartCommand(),
|
|
61
|
+
"status": StatusCommand(),
|
|
62
|
+
"test": TestCommand(),
|
|
63
|
+
"add": AddCommand(),
|
|
64
|
+
"remove": RemoveCommand(),
|
|
65
|
+
"logs": LogsCommand(),
|
|
66
|
+
"search": SearchCommand(),
|
|
67
|
+
"install": InstallCommand(),
|
|
68
|
+
"help": HelpCommand(),
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
logger.info("MCPCommandHandler initialized with all command modules")
|
|
72
|
+
|
|
73
|
+
def handle_mcp_command(self, command: str) -> bool:
|
|
74
|
+
"""
|
|
75
|
+
Handle MCP commands and route to appropriate handler.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
command: The full command string (e.g., "/mcp list", "/mcp start server")
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
True if command was handled successfully, False otherwise
|
|
82
|
+
"""
|
|
83
|
+
group_id = self.generate_group_id()
|
|
84
|
+
|
|
85
|
+
try:
|
|
86
|
+
# Remove /mcp prefix and parse arguments
|
|
87
|
+
command = command.strip()
|
|
88
|
+
if not command.startswith("/mcp"):
|
|
89
|
+
return False
|
|
90
|
+
|
|
91
|
+
# Remove the /mcp prefix
|
|
92
|
+
args_str = command[4:].strip()
|
|
93
|
+
|
|
94
|
+
# If no subcommand, show status dashboard
|
|
95
|
+
if not args_str:
|
|
96
|
+
self._commands["list"].execute([], group_id=group_id)
|
|
97
|
+
return True
|
|
98
|
+
|
|
99
|
+
# Parse arguments using shlex for proper handling of quoted strings
|
|
100
|
+
try:
|
|
101
|
+
args = shlex.split(args_str)
|
|
102
|
+
except ValueError as e:
|
|
103
|
+
emit_info(
|
|
104
|
+
f"[red]Invalid command syntax: {e}[/red]", message_group=group_id
|
|
105
|
+
)
|
|
106
|
+
return True
|
|
107
|
+
|
|
108
|
+
if not args:
|
|
109
|
+
self._commands["list"].execute([], group_id=group_id)
|
|
110
|
+
return True
|
|
111
|
+
|
|
112
|
+
subcommand = args[0].lower()
|
|
113
|
+
sub_args = args[1:] if len(args) > 1 else []
|
|
114
|
+
|
|
115
|
+
# Route to appropriate command handler
|
|
116
|
+
command_handler = self._commands.get(subcommand)
|
|
117
|
+
if command_handler:
|
|
118
|
+
command_handler.execute(sub_args, group_id=group_id)
|
|
119
|
+
return True
|
|
120
|
+
else:
|
|
121
|
+
emit_info(
|
|
122
|
+
f"[yellow]Unknown MCP subcommand: {subcommand}[/yellow]",
|
|
123
|
+
message_group=group_id,
|
|
124
|
+
)
|
|
125
|
+
emit_info(
|
|
126
|
+
"Type '/mcp help' for available commands", message_group=group_id
|
|
127
|
+
)
|
|
128
|
+
return True
|
|
129
|
+
|
|
130
|
+
except Exception as e:
|
|
131
|
+
logger.error(f"Error handling MCP command '{command}': {e}")
|
|
132
|
+
emit_info(f"Error executing MCP command: {e}", message_group=group_id)
|
|
133
|
+
return True
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP Help Command - Shows help for all MCP commands.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import List, Optional
|
|
7
|
+
|
|
8
|
+
from rich.text import Text
|
|
9
|
+
|
|
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 HelpCommand(MCPCommandBase):
|
|
19
|
+
"""
|
|
20
|
+
Command handler for showing MCP command help.
|
|
21
|
+
|
|
22
|
+
Displays comprehensive help information for all available MCP commands.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def execute(self, args: List[str], group_id: Optional[str] = None) -> None:
|
|
26
|
+
"""
|
|
27
|
+
Show help for MCP commands.
|
|
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
|
+
# Build help text programmatically to avoid markup conflicts
|
|
38
|
+
help_lines = []
|
|
39
|
+
|
|
40
|
+
# Title
|
|
41
|
+
help_lines.append(
|
|
42
|
+
Text("MCP Server Management Commands", style="bold magenta")
|
|
43
|
+
)
|
|
44
|
+
help_lines.append(Text(""))
|
|
45
|
+
|
|
46
|
+
# Registry Commands
|
|
47
|
+
help_lines.append(Text("Registry Commands:", style="bold cyan"))
|
|
48
|
+
help_lines.append(
|
|
49
|
+
Text("/mcp search", style="cyan")
|
|
50
|
+
+ Text(" [query] Search 30+ pre-configured servers")
|
|
51
|
+
)
|
|
52
|
+
help_lines.append(
|
|
53
|
+
Text("/mcp install", style="cyan")
|
|
54
|
+
+ Text(" <id> Install server from registry")
|
|
55
|
+
)
|
|
56
|
+
help_lines.append(Text(""))
|
|
57
|
+
|
|
58
|
+
# Core Commands
|
|
59
|
+
help_lines.append(Text("Core Commands:", style="bold cyan"))
|
|
60
|
+
help_lines.append(
|
|
61
|
+
Text("/mcp", style="cyan")
|
|
62
|
+
+ Text(" Show server status dashboard")
|
|
63
|
+
)
|
|
64
|
+
help_lines.append(
|
|
65
|
+
Text("/mcp list", style="cyan")
|
|
66
|
+
+ Text(" List all registered servers")
|
|
67
|
+
)
|
|
68
|
+
help_lines.append(
|
|
69
|
+
Text("/mcp start", style="cyan")
|
|
70
|
+
+ Text(" <name> Start a specific server")
|
|
71
|
+
)
|
|
72
|
+
help_lines.append(
|
|
73
|
+
Text("/mcp start-all", style="cyan")
|
|
74
|
+
+ Text(" Start all servers")
|
|
75
|
+
)
|
|
76
|
+
help_lines.append(
|
|
77
|
+
Text("/mcp stop", style="cyan")
|
|
78
|
+
+ Text(" <name> Stop a specific server")
|
|
79
|
+
)
|
|
80
|
+
help_lines.append(
|
|
81
|
+
Text("/mcp stop-all", style="cyan")
|
|
82
|
+
+ Text(" [group_id] Stop all running servers")
|
|
83
|
+
)
|
|
84
|
+
help_lines.append(
|
|
85
|
+
Text("/mcp restart", style="cyan")
|
|
86
|
+
+ Text(" <name> Restart a specific server")
|
|
87
|
+
)
|
|
88
|
+
help_lines.append(Text(""))
|
|
89
|
+
|
|
90
|
+
# Management Commands
|
|
91
|
+
help_lines.append(Text("Management Commands:", style="bold cyan"))
|
|
92
|
+
help_lines.append(
|
|
93
|
+
Text("/mcp status", style="cyan")
|
|
94
|
+
+ Text(" [name] Show detailed status (all servers or specific)")
|
|
95
|
+
)
|
|
96
|
+
help_lines.append(
|
|
97
|
+
Text("/mcp test", style="cyan")
|
|
98
|
+
+ Text(" <name> Test connectivity to a server")
|
|
99
|
+
)
|
|
100
|
+
help_lines.append(
|
|
101
|
+
Text("/mcp logs", style="cyan")
|
|
102
|
+
+ Text(" <name> [limit] Show recent events (default limit: 10)")
|
|
103
|
+
)
|
|
104
|
+
help_lines.append(
|
|
105
|
+
Text("/mcp add", style="cyan")
|
|
106
|
+
+ Text(" [json] Add new server (JSON or wizard)")
|
|
107
|
+
)
|
|
108
|
+
help_lines.append(
|
|
109
|
+
Text("/mcp remove", style="cyan")
|
|
110
|
+
+ Text(" <name> Remove/disable a server")
|
|
111
|
+
)
|
|
112
|
+
help_lines.append(
|
|
113
|
+
Text("/mcp help", style="cyan")
|
|
114
|
+
+ Text(" Show this help message")
|
|
115
|
+
)
|
|
116
|
+
help_lines.append(Text(""))
|
|
117
|
+
|
|
118
|
+
# Status Indicators
|
|
119
|
+
help_lines.append(Text("Status Indicators:", style="bold"))
|
|
120
|
+
help_lines.append(
|
|
121
|
+
Text("✓ Running ✗ Stopped ⚠ Error ⏸ Quarantined ⭐ Popular")
|
|
122
|
+
)
|
|
123
|
+
help_lines.append(Text(""))
|
|
124
|
+
|
|
125
|
+
# Examples
|
|
126
|
+
help_lines.append(Text("Examples:", style="bold"))
|
|
127
|
+
examples_text = """/mcp search database # Find database servers
|
|
128
|
+
/mcp install postgres # Install PostgreSQL server
|
|
129
|
+
/mcp start filesystem # Start a specific server
|
|
130
|
+
/mcp start-all # Start all servers at once
|
|
131
|
+
/mcp stop-all # Stop all running servers
|
|
132
|
+
/mcp add {"name": "test", "type": "stdio", "command": "echo"}"""
|
|
133
|
+
help_lines.append(Text(examples_text, style="dim"))
|
|
134
|
+
|
|
135
|
+
# Combine all lines
|
|
136
|
+
final_text = Text()
|
|
137
|
+
for i, line in enumerate(help_lines):
|
|
138
|
+
if i > 0:
|
|
139
|
+
final_text.append("\n")
|
|
140
|
+
final_text.append_text(line)
|
|
141
|
+
|
|
142
|
+
emit_info(final_text, message_group=group_id)
|
|
143
|
+
|
|
144
|
+
except Exception as e:
|
|
145
|
+
logger.error(f"Error showing help: {e}")
|
|
146
|
+
emit_info(f"[red]Error showing help: {e}[/red]", message_group=group_id)
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP Install Command - Installs pre-configured MCP servers from the registry.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import List, Optional
|
|
7
|
+
|
|
8
|
+
from code_puppy.messaging import emit_info
|
|
9
|
+
from code_puppy.state_management import is_tui_mode
|
|
10
|
+
|
|
11
|
+
from .base import MCPCommandBase
|
|
12
|
+
from .wizard_utils import run_interactive_install_wizard
|
|
13
|
+
|
|
14
|
+
# Configure logging
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class InstallCommand(MCPCommandBase):
|
|
19
|
+
"""
|
|
20
|
+
Command handler for installing MCP servers from registry.
|
|
21
|
+
|
|
22
|
+
Installs pre-configured MCP servers with optional interactive wizard.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def execute(self, args: List[str], group_id: Optional[str] = None) -> None:
|
|
26
|
+
"""
|
|
27
|
+
Install a pre-configured MCP server from the registry.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
args: Server ID and optional custom name
|
|
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
|
+
# If in TUI mode, show message to use Ctrl+T
|
|
38
|
+
if is_tui_mode():
|
|
39
|
+
emit_info(
|
|
40
|
+
"In TUI mode, use Ctrl+T to open the MCP Install Wizard",
|
|
41
|
+
message_group=group_id,
|
|
42
|
+
)
|
|
43
|
+
return
|
|
44
|
+
|
|
45
|
+
# In interactive mode, use the comprehensive installer
|
|
46
|
+
if not args:
|
|
47
|
+
# No args - launch interactive wizard
|
|
48
|
+
success = run_interactive_install_wizard(self.manager, group_id)
|
|
49
|
+
if success:
|
|
50
|
+
try:
|
|
51
|
+
from code_puppy.agent import reload_mcp_servers
|
|
52
|
+
|
|
53
|
+
reload_mcp_servers()
|
|
54
|
+
except ImportError:
|
|
55
|
+
pass
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
# Has args - install directly from catalog
|
|
59
|
+
server_id = args[0]
|
|
60
|
+
success = self._install_from_catalog(server_id, group_id)
|
|
61
|
+
if success:
|
|
62
|
+
try:
|
|
63
|
+
from code_puppy.agent import reload_mcp_servers
|
|
64
|
+
|
|
65
|
+
reload_mcp_servers()
|
|
66
|
+
except ImportError:
|
|
67
|
+
pass
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
except ImportError:
|
|
71
|
+
emit_info("Server registry not available", message_group=group_id)
|
|
72
|
+
except Exception as e:
|
|
73
|
+
logger.error(f"Error installing server: {e}")
|
|
74
|
+
emit_info(f"Installation failed: {e}", message_group=group_id)
|
|
75
|
+
|
|
76
|
+
def _install_from_catalog(self, server_name_or_id: str, group_id: str) -> bool:
|
|
77
|
+
"""Install a server directly from the catalog by name or ID."""
|
|
78
|
+
try:
|
|
79
|
+
from code_puppy.mcp.server_registry_catalog import catalog
|
|
80
|
+
from code_puppy.messaging import emit_prompt
|
|
81
|
+
|
|
82
|
+
from .utils import find_server_id_by_name
|
|
83
|
+
from .wizard_utils import install_server_from_catalog
|
|
84
|
+
|
|
85
|
+
# Try to find server by ID first, then by name/search
|
|
86
|
+
selected_server = catalog.get_by_id(server_name_or_id)
|
|
87
|
+
|
|
88
|
+
if not selected_server:
|
|
89
|
+
# Try searching by name
|
|
90
|
+
results = catalog.search(server_name_or_id)
|
|
91
|
+
if not results:
|
|
92
|
+
emit_info(
|
|
93
|
+
f"❌ No server found matching '{server_name_or_id}'",
|
|
94
|
+
message_group=group_id,
|
|
95
|
+
)
|
|
96
|
+
emit_info(
|
|
97
|
+
"Try '/mcp install' to browse available servers",
|
|
98
|
+
message_group=group_id,
|
|
99
|
+
)
|
|
100
|
+
return False
|
|
101
|
+
elif len(results) == 1:
|
|
102
|
+
selected_server = results[0]
|
|
103
|
+
else:
|
|
104
|
+
# Multiple matches, show them
|
|
105
|
+
emit_info(
|
|
106
|
+
f"🔍 Multiple servers found matching '{server_name_or_id}':",
|
|
107
|
+
message_group=group_id,
|
|
108
|
+
)
|
|
109
|
+
for i, server in enumerate(results[:5]):
|
|
110
|
+
indicators = []
|
|
111
|
+
if server.verified:
|
|
112
|
+
indicators.append("✓")
|
|
113
|
+
if server.popular:
|
|
114
|
+
indicators.append("⭐")
|
|
115
|
+
|
|
116
|
+
indicator_str = ""
|
|
117
|
+
if indicators:
|
|
118
|
+
indicator_str = " " + "".join(indicators)
|
|
119
|
+
|
|
120
|
+
emit_info(
|
|
121
|
+
f" {i + 1}. {server.display_name}{indicator_str}",
|
|
122
|
+
message_group=group_id,
|
|
123
|
+
)
|
|
124
|
+
emit_info(f" ID: {server.id}", message_group=group_id)
|
|
125
|
+
|
|
126
|
+
emit_info(
|
|
127
|
+
"Please use the exact server ID: '/mcp install <server_id>'",
|
|
128
|
+
message_group=group_id,
|
|
129
|
+
)
|
|
130
|
+
return False
|
|
131
|
+
|
|
132
|
+
# Show what we're installing
|
|
133
|
+
emit_info(
|
|
134
|
+
f"📦 Installing: {selected_server.display_name}", message_group=group_id
|
|
135
|
+
)
|
|
136
|
+
description = (
|
|
137
|
+
selected_server.description
|
|
138
|
+
if selected_server.description
|
|
139
|
+
else "No description available"
|
|
140
|
+
)
|
|
141
|
+
emit_info(f"Description: {description}", message_group=group_id)
|
|
142
|
+
emit_info("", message_group=group_id)
|
|
143
|
+
|
|
144
|
+
# Get custom name (default to server name)
|
|
145
|
+
server_name = emit_prompt(
|
|
146
|
+
f"Enter custom name for this server [{selected_server.name}]: "
|
|
147
|
+
).strip()
|
|
148
|
+
if not server_name:
|
|
149
|
+
server_name = selected_server.name
|
|
150
|
+
|
|
151
|
+
# Check if name already exists
|
|
152
|
+
existing_server = find_server_id_by_name(self.manager, server_name)
|
|
153
|
+
if existing_server:
|
|
154
|
+
override = emit_prompt(
|
|
155
|
+
f"Server '{server_name}' already exists. Override it? [y/N]: "
|
|
156
|
+
)
|
|
157
|
+
if not override.lower().startswith("y"):
|
|
158
|
+
emit_info("Installation cancelled", message_group=group_id)
|
|
159
|
+
return False
|
|
160
|
+
|
|
161
|
+
# Install with default configuration (simplified)
|
|
162
|
+
env_vars = {}
|
|
163
|
+
cmd_args = {}
|
|
164
|
+
|
|
165
|
+
# Install the server
|
|
166
|
+
return install_server_from_catalog(
|
|
167
|
+
self.manager, selected_server, server_name, env_vars, cmd_args, group_id
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
except ImportError:
|
|
171
|
+
emit_info("Server catalog not available", message_group=group_id)
|
|
172
|
+
return False
|
|
173
|
+
except Exception as e:
|
|
174
|
+
logger.error(f"Error installing from catalog: {e}")
|
|
175
|
+
emit_info(f"[red]Installation error: {e}[/red]", message_group=group_id)
|
|
176
|
+
return False
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP List Command - Lists all registered MCP servers in a formatted table.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import List, Optional
|
|
7
|
+
|
|
8
|
+
from rich.table import Table
|
|
9
|
+
from rich.text import Text
|
|
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 .utils import format_state_indicator, format_uptime
|
|
16
|
+
|
|
17
|
+
# Configure logging
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ListCommand(MCPCommandBase):
|
|
22
|
+
"""
|
|
23
|
+
Command handler for listing MCP servers.
|
|
24
|
+
|
|
25
|
+
Displays all registered MCP servers in a formatted table with status information.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def execute(self, args: List[str], group_id: Optional[str] = None) -> None:
|
|
29
|
+
"""
|
|
30
|
+
List all registered MCP servers in a formatted table.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
args: Command arguments (unused for list command)
|
|
34
|
+
group_id: Optional message group ID for grouping related messages
|
|
35
|
+
"""
|
|
36
|
+
if group_id is None:
|
|
37
|
+
group_id = self.generate_group_id()
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
servers = self.manager.list_servers()
|
|
41
|
+
|
|
42
|
+
if not servers:
|
|
43
|
+
emit_info("No MCP servers registered", message_group=group_id)
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
# Create table for server list
|
|
47
|
+
table = Table(title="🔌 MCP Server Status Dashboard")
|
|
48
|
+
table.add_column("Name", style="cyan", no_wrap=True)
|
|
49
|
+
table.add_column("Type", style="dim", no_wrap=True)
|
|
50
|
+
table.add_column("State", justify="center")
|
|
51
|
+
table.add_column("Enabled", justify="center")
|
|
52
|
+
table.add_column("Uptime", style="dim")
|
|
53
|
+
table.add_column("Status", style="dim")
|
|
54
|
+
|
|
55
|
+
for server in servers:
|
|
56
|
+
# Format state with appropriate color and icon
|
|
57
|
+
state_display = format_state_indicator(server.state)
|
|
58
|
+
|
|
59
|
+
# Format enabled status
|
|
60
|
+
enabled_display = "✓" if server.enabled else "✗"
|
|
61
|
+
enabled_style = "green" if server.enabled else "red"
|
|
62
|
+
|
|
63
|
+
# Format uptime
|
|
64
|
+
uptime_display = format_uptime(server.uptime_seconds)
|
|
65
|
+
|
|
66
|
+
# Format status message
|
|
67
|
+
status_display = server.error_message or "OK"
|
|
68
|
+
if server.quarantined:
|
|
69
|
+
status_display = "Quarantined"
|
|
70
|
+
|
|
71
|
+
table.add_row(
|
|
72
|
+
server.name,
|
|
73
|
+
server.type.upper(),
|
|
74
|
+
state_display,
|
|
75
|
+
Text(enabled_display, style=enabled_style),
|
|
76
|
+
uptime_display,
|
|
77
|
+
status_display,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
emit_info(table, message_group=group_id)
|
|
81
|
+
|
|
82
|
+
# Show summary
|
|
83
|
+
total = len(servers)
|
|
84
|
+
running = sum(
|
|
85
|
+
1 for s in servers if s.state == ServerState.RUNNING and s.enabled
|
|
86
|
+
)
|
|
87
|
+
emit_info(
|
|
88
|
+
f"\n📊 Summary: {running}/{total} servers running",
|
|
89
|
+
message_group=group_id,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
except Exception as e:
|
|
93
|
+
logger.error(f"Error listing MCP servers: {e}")
|
|
94
|
+
emit_info(f"[red]Error listing servers: {e}[/red]", message_group=group_id)
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP Logs Command - Shows recent events/logs for a server.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from typing import List, Optional
|
|
8
|
+
|
|
9
|
+
from rich.table import Table
|
|
10
|
+
from rich.text import Text
|
|
11
|
+
|
|
12
|
+
from code_puppy.messaging import emit_info
|
|
13
|
+
|
|
14
|
+
from .base import MCPCommandBase
|
|
15
|
+
from .utils import find_server_id_by_name, suggest_similar_servers
|
|
16
|
+
|
|
17
|
+
# Configure logging
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class LogsCommand(MCPCommandBase):
|
|
22
|
+
"""
|
|
23
|
+
Command handler for showing MCP server logs.
|
|
24
|
+
|
|
25
|
+
Shows recent events/logs for a specific MCP server with configurable limit.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def execute(self, args: List[str], group_id: Optional[str] = None) -> None:
|
|
29
|
+
"""
|
|
30
|
+
Show recent events/logs for a server.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
args: Command arguments, expects [server_name] and optional [limit]
|
|
34
|
+
group_id: Optional message group ID for grouping related messages
|
|
35
|
+
"""
|
|
36
|
+
if group_id is None:
|
|
37
|
+
group_id = self.generate_group_id()
|
|
38
|
+
|
|
39
|
+
if not args:
|
|
40
|
+
emit_info("Usage: /mcp logs <server_name> [limit]", message_group=group_id)
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
server_name = args[0]
|
|
44
|
+
limit = 10 # Default limit
|
|
45
|
+
|
|
46
|
+
if len(args) > 1:
|
|
47
|
+
try:
|
|
48
|
+
limit = int(args[1])
|
|
49
|
+
if limit <= 0 or limit > 100:
|
|
50
|
+
emit_info(
|
|
51
|
+
"Limit must be between 1 and 100, using default: 10",
|
|
52
|
+
message_group=group_id,
|
|
53
|
+
)
|
|
54
|
+
limit = 10
|
|
55
|
+
except ValueError:
|
|
56
|
+
emit_info(
|
|
57
|
+
f"Invalid limit '{args[1]}', using default: 10",
|
|
58
|
+
message_group=group_id,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
# Find server by name
|
|
63
|
+
server_id = find_server_id_by_name(self.manager, server_name)
|
|
64
|
+
if not server_id:
|
|
65
|
+
emit_info(f"Server '{server_name}' not found", message_group=group_id)
|
|
66
|
+
suggest_similar_servers(self.manager, server_name, group_id=group_id)
|
|
67
|
+
return
|
|
68
|
+
|
|
69
|
+
# Get server status which includes recent events
|
|
70
|
+
status = self.manager.get_server_status(server_id)
|
|
71
|
+
|
|
72
|
+
if not status.get("exists", True):
|
|
73
|
+
emit_info(
|
|
74
|
+
f"Server '{server_name}' status not available",
|
|
75
|
+
message_group=group_id,
|
|
76
|
+
)
|
|
77
|
+
return
|
|
78
|
+
|
|
79
|
+
recent_events = status.get("recent_events", [])
|
|
80
|
+
|
|
81
|
+
if not recent_events:
|
|
82
|
+
emit_info(
|
|
83
|
+
f"No recent events for server: {server_name}",
|
|
84
|
+
message_group=group_id,
|
|
85
|
+
)
|
|
86
|
+
return
|
|
87
|
+
|
|
88
|
+
# Show events in a table
|
|
89
|
+
table = Table(title=f"📋 Recent Events for {server_name} (last {limit})")
|
|
90
|
+
table.add_column("Time", style="dim", no_wrap=True)
|
|
91
|
+
table.add_column("Event", style="cyan")
|
|
92
|
+
table.add_column("Details", style="dim")
|
|
93
|
+
|
|
94
|
+
# Take only the requested number of events
|
|
95
|
+
events_to_show = (
|
|
96
|
+
recent_events[-limit:] if len(recent_events) > limit else recent_events
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
for event in reversed(events_to_show): # Show newest first
|
|
100
|
+
timestamp = datetime.fromisoformat(event["timestamp"])
|
|
101
|
+
time_str = timestamp.strftime("%H:%M:%S")
|
|
102
|
+
event_type = event["event_type"]
|
|
103
|
+
|
|
104
|
+
# Format details
|
|
105
|
+
details = event.get("details", {})
|
|
106
|
+
details_str = details.get("message", "")
|
|
107
|
+
if not details_str and "error" in details:
|
|
108
|
+
details_str = str(details["error"])
|
|
109
|
+
|
|
110
|
+
# Color code event types
|
|
111
|
+
event_style = "cyan"
|
|
112
|
+
if "error" in event_type.lower():
|
|
113
|
+
event_style = "red"
|
|
114
|
+
elif event_type in ["started", "enabled", "registered"]:
|
|
115
|
+
event_style = "green"
|
|
116
|
+
elif event_type in ["stopped", "disabled"]:
|
|
117
|
+
event_style = "yellow"
|
|
118
|
+
|
|
119
|
+
table.add_row(
|
|
120
|
+
time_str, Text(event_type, style=event_style), details_str or "-"
|
|
121
|
+
)
|
|
122
|
+
emit_info(table, message_group=group_id)
|
|
123
|
+
|
|
124
|
+
except Exception as e:
|
|
125
|
+
logger.error(f"Error getting logs for server '{server_name}': {e}")
|
|
126
|
+
emit_info(f"[red]Error getting logs: {e}[/red]", message_group=group_id)
|