1- use std:: collections:: HashMap ;
1+ use std:: collections:: {
2+ BTreeMap ,
3+ HashMap ,
4+ } ;
25use std:: io:: Write ;
36use std:: path:: PathBuf ;
47use std:: process:: ExitCode ;
@@ -8,6 +11,7 @@ use clap::{
811 Args ,
912 ValueEnum ,
1013} ;
14+ use crossterm:: style:: Stylize ;
1115use crossterm:: {
1216 execute,
1317 style,
@@ -20,6 +24,7 @@ use eyre::{
2024use super :: agent:: {
2125 Agent ,
2226 Agents ,
27+ DEFAULT_AGENT_NAME ,
2328 McpServerConfig ,
2429} ;
2530use crate :: cli:: chat:: tool_manager:: {
@@ -33,15 +38,17 @@ use crate::cli::chat::tools::custom_tool::{
3338use crate :: os:: Os ;
3439use crate :: util:: directories;
3540
36- #[ derive( Debug , Copy , Clone , PartialEq , Eq , ValueEnum ) ]
41+ #[ derive( Debug , Copy , Clone , PartialEq , Eq , PartialOrd , Ord , ValueEnum , Hash ) ]
3742pub enum Scope {
43+ Default ,
3844 Workspace ,
3945 Global ,
4046}
4147
4248impl std:: fmt:: Display for Scope {
4349 fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
4450 match self {
51+ Scope :: Default => write ! ( f, "default" ) ,
4552 Scope :: Workspace => write ! ( f, "workspace" ) ,
4653 Scope :: Global => write ! ( f, "global" ) ,
4754 }
@@ -247,31 +254,43 @@ impl RemoveArgs {
247254pub struct ListArgs {
248255 #[ arg( value_enum) ]
249256 pub scope : Option < Scope > ,
250- #[ arg( long, hide = true ) ]
251- pub profile : Option < String > ,
252257}
253258
254259impl ListArgs {
255260 pub async fn execute ( self , os : & mut Os , output : & mut impl Write ) -> Result < ( ) > {
256- let configs = get_mcp_server_configs ( os, self . scope ) . await ?;
261+ let mut configs = get_mcp_server_configs ( os) . await ?;
262+ configs. retain ( |k, _| self . scope . is_none_or ( |s| s == * k) ) ;
257263 if configs. is_empty ( ) {
258264 writeln ! ( output, "No MCP server configurations found.\n " ) ?;
259265 return Ok ( ( ) ) ;
260266 }
261267
262- for ( scope, path, cfg_opt) in configs {
268+ for ( scope, agents) in configs {
269+ if let Some ( s) = self . scope {
270+ if scope != s {
271+ continue ;
272+ }
273+ }
263274 writeln ! ( output) ?;
264- writeln ! ( output, "{}:\n {}" , scope_display( & scope) , path. display( ) ) ?;
265- match cfg_opt {
266- Some ( cfg) if !cfg. mcp_servers . is_empty ( ) => {
267- for ( name, tool_cfg) in & cfg. mcp_servers {
268- let status = if tool_cfg. disabled { " (disabled)" } else { "" } ;
269- writeln ! ( output, " • {name:<12} {}{}" , tool_cfg. command, status) ?;
270- }
271- } ,
272- _ => {
273- writeln ! ( output, " (empty)" ) ?;
274- } ,
275+ writeln ! ( output, "{}:\n " , scope_display( & scope) ) ?;
276+ for ( agent_name, cfg_opt, _) in agents {
277+ writeln ! ( output, " {}" , agent_name. bold( ) ) ?;
278+ match cfg_opt {
279+ Some ( cfg) if !cfg. mcp_servers . is_empty ( ) => {
280+ // Sorting servers by name since HashMap is unordered, and having a bunch
281+ // of agents with the same global MCP servers included with different
282+ // ordering looks weird.
283+ let mut servers = cfg. mcp_servers . into_iter ( ) . collect :: < Vec < _ > > ( ) ;
284+ servers. sort_by ( |a, b| a. 0 . cmp ( & b. 0 ) ) ;
285+ for ( name, tool_cfg) in & servers {
286+ let status = if tool_cfg. disabled { " (disabled)" } else { "" } ;
287+ writeln ! ( output, " • {name:<12} {}{}" , tool_cfg. command, status) ?;
288+ }
289+ } ,
290+ _ => {
291+ writeln ! ( output, " (empty)" ) ?;
292+ } ,
293+ }
275294 }
276295 }
277296 writeln ! ( output, "\n " ) ?;
@@ -337,50 +356,49 @@ pub struct StatusArgs {
337356
338357impl StatusArgs {
339358 pub async fn execute ( self , os : & mut Os , output : & mut impl Write ) -> Result < ( ) > {
340- let configs = get_mcp_server_configs ( os, None ) . await ?;
359+ let configs = get_mcp_server_configs ( os) . await ?;
341360 let mut found = false ;
342361
343- for ( sc, path, cfg_opt) in configs {
344- if let Some ( cfg) = cfg_opt. and_then ( |c| c. mcp_servers . get ( & self . name ) . cloned ( ) ) {
345- found = true ;
346- execute ! (
347- output,
348- style:: Print ( "\n ─────────────\n " ) ,
349- style:: Print ( format!( "Scope : {}\n " , scope_display( & sc) ) ) ,
350- style:: Print ( format!( "File : {}\n " , path. display( ) ) ) ,
351- style:: Print ( format!( "Command : {}\n " , cfg. command) ) ,
352- style:: Print ( format!( "Timeout : {} ms\n " , cfg. timeout) ) ,
353- style:: Print ( format!( "Disabled: {}\n " , cfg. disabled) ) ,
354- style:: Print ( format!(
355- "Env Vars: {}\n " ,
356- cfg. env
357- . as_ref( )
358- . map_or_else( || "(none)" . into( ) , |e| e. keys( ) . cloned( ) . collect:: <Vec <_>>( ) . join( ", " ) )
359- ) ) ,
360- ) ?;
362+ for ( sc, agents) in configs {
363+ for ( name, cfg_opt, _) in agents {
364+ if let Some ( cfg) = cfg_opt. and_then ( |c| c. mcp_servers . get ( & self . name ) . cloned ( ) ) {
365+ found = true ;
366+ execute ! (
367+ output,
368+ style:: Print ( "\n ─────────────\n " ) ,
369+ style:: Print ( format!( "Scope : {}\n " , scope_display( & sc) ) ) ,
370+ style:: Print ( format!( "Agent : {}\n " , name) ) ,
371+ style:: Print ( format!( "Command : {}\n " , cfg. command) ) ,
372+ style:: Print ( format!( "Timeout : {} ms\n " , cfg. timeout) ) ,
373+ style:: Print ( format!( "Disabled: {}\n " , cfg. disabled) ) ,
374+ style:: Print ( format!(
375+ "Env Vars: {}\n " ,
376+ cfg. env. as_ref( ) . map_or_else(
377+ || "(none)" . into( ) ,
378+ |e| e
379+ . iter( )
380+ . map( |( k, v) | format!( "{}={}" , k, v) )
381+ . collect:: <Vec <_>>( )
382+ . join( ", " )
383+ )
384+ ) ) ,
385+ ) ?;
386+ }
361387 }
388+ writeln ! ( output, "\n " ) ?;
362389 }
363- writeln ! ( output, "\n " ) ?;
364390
365391 if !found {
366- bail ! ( "No MCP server named '{}' found in any scope/profile \n " , self . name) ;
392+ bail ! ( "No MCP server named '{}' found in any agent \n " , self . name) ;
367393 }
368394
369395 Ok ( ( ) )
370396 }
371397}
372398
373- async fn get_mcp_server_configs (
374- os : & mut Os ,
375- scope : Option < Scope > ,
376- ) -> Result < Vec < ( Scope , PathBuf , Option < McpServerConfig > ) > > {
377- let mut targets = Vec :: new ( ) ;
378- match scope {
379- Some ( scope) => targets. push ( scope) ,
380- None => targets. extend ( [ Scope :: Workspace , Scope :: Global ] ) ,
381- }
382-
383- let mut results = Vec :: new ( ) ;
399+ /// Returns a [BTreeMap] for consistent key iteration.
400+ async fn get_mcp_server_configs ( os : & mut Os ) -> Result < BTreeMap < Scope , Vec < ( String , Option < McpServerConfig > , bool ) > > > {
401+ let mut results = BTreeMap :: new ( ) ;
384402 let mut stderr = std:: io:: stderr ( ) ;
385403 let agents = Agents :: load ( os, None , true , & mut stderr) . await . 0 ;
386404 let global_path = directories:: chat_global_agent_path ( os) ?;
@@ -391,21 +409,28 @@ async fn get_mcp_server_configs(
391409 . is_some_and ( |p| p. parent ( ) . is_some_and ( |p| p == global_path) )
392410 {
393411 Scope :: Global
412+ } else if agent. name == DEFAULT_AGENT_NAME {
413+ Scope :: Default
394414 } else {
395415 Scope :: Workspace
396416 } ;
397-
398- results. push ( (
399- scope,
400- agent. path . ok_or ( eyre:: eyre!( "Agent missing path info" ) ) ?,
417+ results. entry ( scope) . or_insert ( Vec :: new ( ) ) . push ( (
418+ agent. name ,
401419 Some ( agent. mcp_servers ) ,
420+ agent. use_legacy_mcp_json ,
402421 ) ) ;
403422 }
423+
424+ for agents in results. values_mut ( ) {
425+ agents. sort_by ( |a, b| a. 0 . cmp ( & b. 0 ) ) ;
426+ }
427+
404428 Ok ( results)
405429}
406430
407431fn scope_display ( scope : & Scope ) -> String {
408432 match scope {
433+ Scope :: Default => "🤖 default" . into ( ) ,
409434 Scope :: Workspace => "📄 workspace" . into ( ) ,
410435 Scope :: Global => "🌍 global" . into ( ) ,
411436 }
@@ -480,7 +505,7 @@ mod tests {
480505 assert_eq ! (
481506 path. to_str( ) ,
482507 workspace_mcp_config_path( & os) . unwrap( ) . to_str( ) ,
483- "No scope or profile should default to the workspace path"
508+ "No scope should default to the workspace path"
484509 ) ;
485510 }
486511
@@ -625,7 +650,6 @@ mod tests {
625650 [ "mcp" , "list" , "global" ] ,
626651 RootSubcommand :: Mcp ( McpSubcommand :: List ( ListArgs {
627652 scope: Some ( Scope :: Global ) ,
628- profile: None
629653 } ) )
630654 ) ;
631655 }
0 commit comments