@@ -219,94 +219,134 @@ def handle_mcp_discover_servers(args: Namespace) -> int:
219219
220220
221221def handle_mcp_list_hosts (args : Namespace ) -> int :
222- """Handle 'hatch mcp list hosts' command - shows configured hosts in environment.
222+ """Handle 'hatch mcp list hosts' command - host-centric design.
223+
224+ Lists host/server pairs from host configuration files. Shows ALL servers
225+ on hosts (both Hatch-managed and 3rd party) with Hatch management status.
223226
224227 Args:
225228 args: Parsed command-line arguments containing:
226229 - env_manager: HatchEnvironmentManager instance
227- - env: Optional environment name (uses current if not specified)
228- - detailed: Whether to show detailed host information
230+ - server: Optional regex pattern to filter by server name
229231 - json: Optional flag for JSON output
230232
231233 Returns:
232234 int: EXIT_SUCCESS (0) on success, EXIT_ERROR (1) on failure
235+
236+ Reference: R10 §3.1 (10-namespace_consistency_specification_v2.md)
233237 """
234238 try :
235239 import json as json_module
236- from collections import defaultdict
237-
240+ import re
241+ # Import strategies to trigger registration
242+ import hatch .mcp_host_config .strategies
243+
238244 env_manager : HatchEnvironmentManager = args .env_manager
239- env_name : Optional [str ] = getattr (args , 'env' , None )
240- detailed : bool = getattr (args , 'detailed' , False )
245+ server_pattern : Optional [str ] = getattr (args , 'server' , None )
241246 json_output : bool = getattr (args , 'json' , False )
242-
243- # Resolve environment name
244- target_env = env_name or env_manager .get_current_environment ()
245-
246- # Validate environment exists
247- if not env_manager .environment_exists (target_env ):
248- available_envs = env_manager .list_environments ()
249- print (f"Error: Environment '{ target_env } ' does not exist." )
250- if available_envs :
251- print (f"Available environments: { ', ' .join (available_envs )} " )
252- return EXIT_ERROR
253-
254- # Collect hosts from configured_hosts across all packages in environment
255- hosts = defaultdict (int )
256- host_last_sync = {}
257-
258- try :
259- env_data = env_manager .get_environment_data (target_env )
260- packages = env_data .get ("packages" , [])
261-
262- for package in packages :
263- configured_hosts = package .get ("configured_hosts" , {})
264-
265- for host_name , host_config in configured_hosts .items ():
266- hosts [host_name ] += 1
267- # Track most recent sync time
268- configured_at = host_config .get ("configured_at" , "N/A" )
269- if host_name not in host_last_sync or configured_at > host_last_sync .get (host_name , "" ):
270- host_last_sync [host_name ] = configured_at
271-
272- except Exception as e :
273- print (f"Error reading environment data: { e } " )
274- return EXIT_ERROR
275-
276- # JSON output
247+
248+ # Compile regex pattern if provided
249+ pattern_re = None
250+ if server_pattern :
251+ try :
252+ pattern_re = re .compile (server_pattern )
253+ except re .error as e :
254+ print (f"Error: Invalid regex pattern '{ server_pattern } ': { e } " )
255+ return EXIT_ERROR
256+
257+ # Build Hatch management lookup: {server_name: {host: env_name}}
258+ hatch_managed = {}
259+ for env_info in env_manager .list_environments ():
260+ env_name = env_info .get ("name" , env_info ) if isinstance (env_info , dict ) else env_info
261+ try :
262+ env_data = env_manager .get_environment_data (env_name )
263+ packages = env_data .get ("packages" , []) if isinstance (env_data , dict ) else getattr (env_data , 'packages' , [])
264+
265+ for pkg in packages :
266+ pkg_name = pkg .get ("name" ) if isinstance (pkg , dict ) else getattr (pkg , 'name' , None )
267+ configured_hosts = pkg .get ("configured_hosts" , {}) if isinstance (pkg , dict ) else getattr (pkg , 'configured_hosts' , {})
268+
269+ if pkg_name :
270+ if pkg_name not in hatch_managed :
271+ hatch_managed [pkg_name ] = {}
272+ for host_name in configured_hosts .keys ():
273+ hatch_managed [pkg_name ][host_name ] = env_name
274+ except Exception :
275+ continue
276+
277+ # Get all available hosts and read their configurations
278+ available_hosts = MCPHostRegistry .detect_available_hosts ()
279+
280+ # Collect host/server pairs from host config files
281+ # Format: (host, server, is_hatch_managed, env_name)
282+ host_rows = []
283+
284+ for host_type in available_hosts :
285+ try :
286+ strategy = MCPHostRegistry .get_strategy (host_type )
287+ host_config = strategy .read_configuration ()
288+ host_name = host_type .value
289+
290+ for server_name , server_config in host_config .servers .items ():
291+ # Apply server pattern filter if specified
292+ if pattern_re and not pattern_re .search (server_name ):
293+ continue
294+
295+ # Check if Hatch-managed
296+ is_hatch_managed = False
297+ env_name = None
298+
299+ if server_name in hatch_managed :
300+ host_info = hatch_managed [server_name ].get (host_name )
301+ if host_info :
302+ is_hatch_managed = True
303+ env_name = host_info
304+
305+ host_rows .append ((host_name , server_name , is_hatch_managed , env_name ))
306+ except Exception :
307+ # Skip hosts that can't be read
308+ continue
309+
310+ # Sort rows by host (alphabetically), then by server
311+ host_rows .sort (key = lambda x : (x [0 ], x [1 ]))
312+
313+ # JSON output per R10 §8
277314 if json_output :
278- hosts_data = []
279- for host_name , package_count in sorted (hosts .items ()):
280- hosts_data .append ({
281- "host" : host_name ,
282- "package_count" : package_count ,
283- "last_synced" : host_last_sync .get (host_name , None )
315+ rows_data = []
316+ for host , server , is_hatch , env in host_rows :
317+ rows_data .append ({
318+ "host" : host ,
319+ "server" : server ,
320+ "hatch_managed" : is_hatch ,
321+ "environment" : env
284322 })
285- print (json_module .dumps ({
286- "environment" : target_env ,
287- "hosts" : hosts_data
288- }, indent = 2 ))
323+ print (json_module .dumps ({"rows" : rows_data }, indent = 2 ))
289324 return EXIT_SUCCESS
290-
325+
291326 # Display results
292- if not hosts :
293- print (f"No configured hosts for environment '{ target_env } '" )
327+ if not host_rows :
328+ if server_pattern :
329+ print (f"No MCP servers matching '{ server_pattern } ' on any host" )
330+ else :
331+ print ("No MCP servers found on any available hosts" )
294332 return EXIT_SUCCESS
295-
296- print (f"Configured Hosts (environment: { target_env } ):" )
297333
298- # Define table columns per R02 §2.4
334+ print ("MCP Hosts:" )
335+
336+ # Define table columns per R10 §3.1: Host → Server → Hatch → Environment
299337 columns = [
300- ColumnDef (name = "Host" , width = 20 ),
301- ColumnDef (name = "Packages" , width = 10 , align = "right" ),
302- ColumnDef (name = "Last Synced" , width = "auto" ),
338+ ColumnDef (name = "Host" , width = 18 ),
339+ ColumnDef (name = "Server" , width = 18 ),
340+ ColumnDef (name = "Hatch" , width = 8 ),
341+ ColumnDef (name = "Environment" , width = 15 ),
303342 ]
304343 formatter = TableFormatter (columns )
305-
306- for host_name , package_count in sorted (hosts .items ()):
307- last_sync = host_last_sync .get (host_name , "N/A" )
308- formatter .add_row ([host_name , str (package_count ), last_sync ])
309-
344+
345+ for host , server , is_hatch , env in host_rows :
346+ hatch_status = "✅" if is_hatch else "❌"
347+ env_display = env if env else "-"
348+ formatter .add_row ([host , server , hatch_status , env_display ])
349+
310350 print (formatter .render ())
311351 return EXIT_SUCCESS
312352 except Exception as e :
0 commit comments