Skip to content

Commit e518e90

Browse files
refactor(cli): extract MCP list handlers to cli_mcp
1 parent de75cf0 commit e518e90

File tree

2 files changed

+206
-156
lines changed

2 files changed

+206
-156
lines changed

hatch/cli/cli_mcp.py

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,191 @@ def handle_mcp_discover_servers(args: Namespace) -> int:
130130
except Exception as e:
131131
print(f"Error discovering servers: {e}")
132132
return EXIT_ERROR
133+
134+
135+
def handle_mcp_list_hosts(args: Namespace) -> int:
136+
"""Handle 'hatch mcp list hosts' command - shows configured hosts in environment.
137+
138+
Args:
139+
args: Parsed command-line arguments containing:
140+
- env_manager: HatchEnvironmentManager instance
141+
- env: Optional environment name (uses current if not specified)
142+
- detailed: Whether to show detailed host information
143+
144+
Returns:
145+
int: EXIT_SUCCESS (0) on success, EXIT_ERROR (1) on failure
146+
"""
147+
try:
148+
from collections import defaultdict
149+
150+
env_manager: HatchEnvironmentManager = args.env_manager
151+
env_name: Optional[str] = getattr(args, 'env', None)
152+
detailed: bool = getattr(args, 'detailed', False)
153+
154+
# Resolve environment name
155+
target_env = env_name or env_manager.get_current_environment()
156+
157+
# Validate environment exists
158+
if not env_manager.environment_exists(target_env):
159+
available_envs = env_manager.list_environments()
160+
print(f"Error: Environment '{target_env}' does not exist.")
161+
if available_envs:
162+
print(f"Available environments: {', '.join(available_envs)}")
163+
return EXIT_ERROR
164+
165+
# Collect hosts from configured_hosts across all packages in environment
166+
hosts = defaultdict(int)
167+
host_details = defaultdict(list)
168+
169+
try:
170+
env_data = env_manager.get_environment_data(target_env)
171+
packages = env_data.get("packages", [])
172+
173+
for package in packages:
174+
package_name = package.get("name", "unknown")
175+
configured_hosts = package.get("configured_hosts", {})
176+
177+
for host_name, host_config in configured_hosts.items():
178+
hosts[host_name] += 1
179+
if detailed:
180+
config_path = host_config.get("config_path", "N/A")
181+
configured_at = host_config.get("configured_at", "N/A")
182+
host_details[host_name].append(
183+
{
184+
"package": package_name,
185+
"config_path": config_path,
186+
"configured_at": configured_at,
187+
}
188+
)
189+
190+
except Exception as e:
191+
print(f"Error reading environment data: {e}")
192+
return EXIT_ERROR
193+
194+
# Display results
195+
if not hosts:
196+
print(f"No configured hosts for environment '{target_env}'")
197+
return EXIT_SUCCESS
198+
199+
print(f"Configured hosts for environment '{target_env}':")
200+
201+
for host_name, package_count in sorted(hosts.items()):
202+
if detailed:
203+
print(f"\n{host_name} ({package_count} packages):")
204+
for detail in host_details[host_name]:
205+
print(f" - Package: {detail['package']}")
206+
print(f" Config path: {detail['config_path']}")
207+
print(f" Configured at: {detail['configured_at']}")
208+
else:
209+
print(f" - {host_name} ({package_count} packages)")
210+
211+
return EXIT_SUCCESS
212+
except Exception as e:
213+
print(f"Error listing hosts: {e}")
214+
return EXIT_ERROR
215+
216+
217+
def handle_mcp_list_servers(args: Namespace) -> int:
218+
"""Handle 'hatch mcp list servers' command.
219+
220+
Args:
221+
args: Parsed command-line arguments containing:
222+
- env_manager: HatchEnvironmentManager instance
223+
- env: Optional environment name (uses current if not specified)
224+
225+
Returns:
226+
int: EXIT_SUCCESS (0) on success, EXIT_ERROR (1) on failure
227+
"""
228+
try:
229+
env_manager: HatchEnvironmentManager = args.env_manager
230+
env_name: Optional[str] = getattr(args, 'env', None)
231+
232+
env_name = env_name or env_manager.get_current_environment()
233+
234+
if not env_manager.environment_exists(env_name):
235+
print(f"Error: Environment '{env_name}' does not exist")
236+
return EXIT_ERROR
237+
238+
packages = env_manager.list_packages(env_name)
239+
mcp_packages = []
240+
241+
for package in packages:
242+
# Check if package has host configuration tracking (indicating MCP server)
243+
configured_hosts = package.get("configured_hosts", {})
244+
if configured_hosts:
245+
# Use the tracked server configuration from any host
246+
first_host = next(iter(configured_hosts.values()))
247+
server_config_data = first_host.get("server_config", {})
248+
249+
# Create a simple server config object
250+
class SimpleServerConfig:
251+
def __init__(self, data):
252+
self.name = data.get("name", package["name"])
253+
self.command = data.get("command", "unknown")
254+
self.args = data.get("args", [])
255+
256+
server_config = SimpleServerConfig(server_config_data)
257+
mcp_packages.append(
258+
{"package": package, "server_config": server_config}
259+
)
260+
else:
261+
# Try the original method as fallback
262+
try:
263+
server_config = get_package_mcp_server_config(
264+
env_manager, env_name, package["name"]
265+
)
266+
mcp_packages.append(
267+
{"package": package, "server_config": server_config}
268+
)
269+
except:
270+
# Package doesn't have MCP server or method failed
271+
continue
272+
273+
if not mcp_packages:
274+
print(f"No MCP servers configured in environment '{env_name}'")
275+
return EXIT_SUCCESS
276+
277+
print(f"MCP servers in environment '{env_name}':")
278+
print(f"{'Server Name':<20} {'Package':<20} {'Version':<10} {'Command'}")
279+
print("-" * 80)
280+
281+
for item in mcp_packages:
282+
package = item["package"]
283+
server_config = item["server_config"]
284+
285+
server_name = server_config.name
286+
package_name = package["name"]
287+
version = package.get("version", "unknown")
288+
command = f"{server_config.command} {' '.join(server_config.args)}"
289+
290+
print(f"{server_name:<20} {package_name:<20} {version:<10} {command}")
291+
292+
# Display host configuration tracking information
293+
configured_hosts = package.get("configured_hosts", {})
294+
if configured_hosts:
295+
print(f"{'':>20} Configured on hosts:")
296+
for hostname, host_config in configured_hosts.items():
297+
config_path = host_config.get("config_path", "unknown")
298+
last_synced = host_config.get("last_synced", "unknown")
299+
# Format the timestamp for better readability
300+
if last_synced != "unknown":
301+
try:
302+
from datetime import datetime
303+
304+
dt = datetime.fromisoformat(
305+
last_synced.replace("Z", "+00:00")
306+
)
307+
last_synced = dt.strftime("%Y-%m-%d %H:%M:%S")
308+
except:
309+
pass # Keep original format if parsing fails
310+
print(
311+
f"{'':>22} - {hostname}: {config_path} (synced: {last_synced})"
312+
)
313+
else:
314+
print(f"{'':>20} No host configurations tracked")
315+
print() # Add blank line between servers
316+
317+
return EXIT_SUCCESS
318+
except Exception as e:
319+
print(f"Error listing servers: {e}")
320+
return EXIT_ERROR

hatch/cli_hatch.py

Lines changed: 18 additions & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@
4747
from hatch.cli.cli_mcp import (
4848
handle_mcp_discover_hosts as _handle_mcp_discover_hosts,
4949
handle_mcp_discover_servers as _handle_mcp_discover_servers,
50+
handle_mcp_list_hosts as _handle_mcp_list_hosts,
51+
handle_mcp_list_servers as _handle_mcp_list_servers,
5052
)
5153

5254

@@ -80,167 +82,27 @@ def handle_mcp_list_hosts(
8082
env_name: Optional[str] = None,
8183
detailed: bool = False,
8284
):
83-
"""Handle 'hatch mcp list hosts' command - shows configured hosts in environment."""
84-
try:
85-
from collections import defaultdict
86-
87-
# Resolve environment name
88-
target_env = env_name or env_manager.get_current_environment()
89-
90-
# Validate environment exists
91-
if not env_manager.environment_exists(target_env):
92-
available_envs = env_manager.list_environments()
93-
print(f"Error: Environment '{target_env}' does not exist.")
94-
if available_envs:
95-
print(f"Available environments: {', '.join(available_envs)}")
96-
return 1
97-
98-
# Collect hosts from configured_hosts across all packages in environment
99-
hosts = defaultdict(int)
100-
host_details = defaultdict(list)
101-
102-
try:
103-
env_data = env_manager.get_environment_data(target_env)
104-
packages = env_data.get("packages", [])
105-
106-
for package in packages:
107-
package_name = package.get("name", "unknown")
108-
configured_hosts = package.get("configured_hosts", {})
109-
110-
for host_name, host_config in configured_hosts.items():
111-
hosts[host_name] += 1
112-
if detailed:
113-
config_path = host_config.get("config_path", "N/A")
114-
configured_at = host_config.get("configured_at", "N/A")
115-
host_details[host_name].append(
116-
{
117-
"package": package_name,
118-
"config_path": config_path,
119-
"configured_at": configured_at,
120-
}
121-
)
122-
123-
except Exception as e:
124-
print(f"Error reading environment data: {e}")
125-
return 1
126-
127-
# Display results
128-
if not hosts:
129-
print(f"No configured hosts for environment '{target_env}'")
130-
return 0
131-
132-
print(f"Configured hosts for environment '{target_env}':")
133-
134-
for host_name, package_count in sorted(hosts.items()):
135-
if detailed:
136-
print(f"\n{host_name} ({package_count} packages):")
137-
for detail in host_details[host_name]:
138-
print(f" - Package: {detail['package']}")
139-
print(f" Config path: {detail['config_path']}")
140-
print(f" Configured at: {detail['configured_at']}")
141-
else:
142-
print(f" - {host_name} ({package_count} packages)")
143-
144-
return 0
145-
except Exception as e:
146-
print(f"Error listing hosts: {e}")
147-
return 1
85+
"""Handle 'hatch mcp list hosts' command - shows configured hosts in environment.
86+
87+
Delegates to hatch.cli.cli_mcp.handle_mcp_list_hosts.
88+
This wrapper maintains backward compatibility during refactoring.
89+
"""
90+
from argparse import Namespace
91+
args = Namespace(env_manager=env_manager, env=env_name, detailed=detailed)
92+
return _handle_mcp_list_hosts(args)
14893

14994

15095
def handle_mcp_list_servers(
15196
env_manager: HatchEnvironmentManager, env_name: Optional[str] = None
15297
):
153-
"""Handle 'hatch mcp list servers' command."""
154-
try:
155-
env_name = env_name or env_manager.get_current_environment()
156-
157-
if not env_manager.environment_exists(env_name):
158-
print(f"Error: Environment '{env_name}' does not exist")
159-
return 1
160-
161-
packages = env_manager.list_packages(env_name)
162-
mcp_packages = []
163-
164-
for package in packages:
165-
# Check if package has host configuration tracking (indicating MCP server)
166-
configured_hosts = package.get("configured_hosts", {})
167-
if configured_hosts:
168-
# Use the tracked server configuration from any host
169-
first_host = next(iter(configured_hosts.values()))
170-
server_config_data = first_host.get("server_config", {})
171-
172-
# Create a simple server config object
173-
class SimpleServerConfig:
174-
def __init__(self, data):
175-
self.name = data.get("name", package["name"])
176-
self.command = data.get("command", "unknown")
177-
self.args = data.get("args", [])
178-
179-
server_config = SimpleServerConfig(server_config_data)
180-
mcp_packages.append(
181-
{"package": package, "server_config": server_config}
182-
)
183-
else:
184-
# Try the original method as fallback
185-
try:
186-
server_config = get_package_mcp_server_config(
187-
env_manager, env_name, package["name"]
188-
)
189-
mcp_packages.append(
190-
{"package": package, "server_config": server_config}
191-
)
192-
except:
193-
# Package doesn't have MCP server or method failed
194-
continue
195-
196-
if not mcp_packages:
197-
print(f"No MCP servers configured in environment '{env_name}'")
198-
return 0
199-
200-
print(f"MCP servers in environment '{env_name}':")
201-
print(f"{'Server Name':<20} {'Package':<20} {'Version':<10} {'Command'}")
202-
print("-" * 80)
203-
204-
for item in mcp_packages:
205-
package = item["package"]
206-
server_config = item["server_config"]
207-
208-
server_name = server_config.name
209-
package_name = package["name"]
210-
version = package.get("version", "unknown")
211-
command = f"{server_config.command} {' '.join(server_config.args)}"
212-
213-
print(f"{server_name:<20} {package_name:<20} {version:<10} {command}")
214-
215-
# Display host configuration tracking information
216-
configured_hosts = package.get("configured_hosts", {})
217-
if configured_hosts:
218-
print(f"{'':>20} Configured on hosts:")
219-
for hostname, host_config in configured_hosts.items():
220-
config_path = host_config.get("config_path", "unknown")
221-
last_synced = host_config.get("last_synced", "unknown")
222-
# Format the timestamp for better readability
223-
if last_synced != "unknown":
224-
try:
225-
from datetime import datetime
226-
227-
dt = datetime.fromisoformat(
228-
last_synced.replace("Z", "+00:00")
229-
)
230-
last_synced = dt.strftime("%Y-%m-%d %H:%M:%S")
231-
except:
232-
pass # Keep original format if parsing fails
233-
print(
234-
f"{'':>22} - {hostname}: {config_path} (synced: {last_synced})"
235-
)
236-
else:
237-
print(f"{'':>20} No host configurations tracked")
238-
print() # Add blank line between servers
239-
240-
return 0
241-
except Exception as e:
242-
print(f"Error listing servers: {e}")
243-
return 1
98+
"""Handle 'hatch mcp list servers' command.
99+
100+
Delegates to hatch.cli.cli_mcp.handle_mcp_list_servers.
101+
This wrapper maintains backward compatibility during refactoring.
102+
"""
103+
from argparse import Namespace
104+
args = Namespace(env_manager=env_manager, env=env_name)
105+
return _handle_mcp_list_servers(args)
244106

245107

246108
def handle_mcp_backup_restore(

0 commit comments

Comments
 (0)