@@ -13,11 +13,12 @@ import (
1313)
1414
1515var (
16- allServers bool
17- longFormat bool
18- showStatus bool
19- toolFilter string
20- allTools bool
16+ allServers bool
17+ longFormat bool
18+ showStatus bool
19+ toolFilter string
20+ allTools bool
21+ commandFormat bool
2122)
2223
2324// listCmd represents the list command
@@ -30,7 +31,8 @@ Without arguments, it lists all default servers.
3031With a profile argument, it lists all servers with that profile.
3132With the -a flag, it lists all servers.
3233With the -l flag, it shows detailed information including command and environment variables.
33- With the -s flag, it shows deployment status across configured tools.` ,
34+ With the -s flag, it shows deployment status across configured tools.
35+ With the -c flag, it shows the executable command with environment variables expanded and inline.` ,
3436 Run : func (cmd * cobra.Command , args []string ) {
3537 config , err := loadComposeFile (composeFile )
3638 if err != nil {
@@ -62,6 +64,7 @@ func init() {
6264 listCmd .Flags ().BoolVarP (& showStatus , "status" , "s" , false , "Show deployment status across configured tools" )
6365 listCmd .Flags ().StringVarP (& toolFilter , "tool" , "t" , "" , "Show status for specific tool only (q-cli, claude-desktop, cursor, kiro)" )
6466 listCmd .Flags ().BoolVar (& allTools , "all-tools" , false , "Show status across all supported tools" )
67+ listCmd .Flags ().BoolVarP (& commandFormat , "command" , "c" , false , "Show executable command with environment variables expanded inline. WARNING: may expose sensitive data such as API keys and secrets" )
6568}
6669
6770func displayServers (servers map [string ]Service ) {
@@ -70,12 +73,26 @@ func displayServers(servers map[string]Service) {
7073 return
7174 }
7275
76+ // Load environment variables if command format flag is set
77+ var envVars map [string ]string
78+ if commandFormat {
79+ var err error
80+ envVars , err = loadEnvVars (composeFile )
81+ if err != nil {
82+ fmt .Fprintf (os .Stderr , "Warning: error loading environment variables: %v\n " , err )
83+ envVars = make (map [string ]string )
84+ }
85+ }
86+
7387 w := tabwriter .NewWriter (os .Stdout , 0 , 0 , 2 , ' ' , 0 )
7488
7589 // Display headers based on format
76- if longFormat {
77- fmt .Fprintln (w , "NAME\t PROFILES\t COMMAND (TYPE)\t ENVVARS" )
78- fmt .Fprintln (w , "----\t --------\t --------------\t -------" )
90+ if commandFormat {
91+ fmt .Fprintln (w , "NAME\t COMMAND" )
92+ fmt .Fprintln (w , "----\t -------" )
93+ } else if longFormat {
94+ fmt .Fprintln (w , "NAME\t PROFILES\t COMMAND\t ENVVARS" )
95+ fmt .Fprintln (w , "----\t --------\t -------\t -------" )
7996 } else {
8097 fmt .Fprintln (w , "NAME\t PROFILES" )
8198 fmt .Fprintln (w , "----\t --------" )
@@ -86,7 +103,7 @@ func displayServers(servers map[string]Service) {
86103 if err != nil {
87104 // If we can't load the file again, just use the map order
88105 for name , service := range servers {
89- printServerRow (w , name , service )
106+ printServerRow (w , name , service , envVars )
90107 }
91108 } else {
92109 // Create two lists: one for default servers and one for non-default servers
@@ -127,20 +144,33 @@ func displayServers(servers map[string]Service) {
127144
128145 // Print default servers first (alphabetically sorted)
129146 for _ , name := range defaultServers {
130- printServerRow (w , name , servers [name ])
147+ printServerRow (w , name , servers [name ], envVars )
131148 }
132149
133150 // Then print other servers (alphabetically sorted)
134151 for _ , name := range otherServers {
135- printServerRow (w , name , servers [name ])
152+ printServerRow (w , name , servers [name ], envVars )
136153 }
137154 }
138155
139156 w .Flush ()
140157}
141158
159+ // shellQuote quotes a string for safe use in shell commands
160+ func shellQuote (s string ) string {
161+ // If the string contains no special characters, return as-is
162+ if ! strings .ContainsAny (s , " \t \n \" '\\ `!" ) {
163+ return s
164+ }
165+ // Use double quotes and escape special characters (but not $, to preserve unexpanded vars)
166+ escaped := strings .ReplaceAll (s , "\\ " , "\\ \\ " )
167+ escaped = strings .ReplaceAll (escaped , "\" " , "\\ \" " )
168+ escaped = strings .ReplaceAll (escaped , "`" , "\\ `" )
169+ return "\" " + escaped + "\" "
170+ }
171+
142172// Helper function to print a single server row
143- func printServerRow (w * tabwriter.Writer , name string , service Service ) {
173+ func printServerRow (w * tabwriter.Writer , name string , service Service , envVars map [ string ] string ) {
144174 // Get profiles
145175 var profiles []string
146176 if profilesStr , ok := service .Labels ["mcp.profile" ]; ok {
@@ -154,15 +184,81 @@ func printServerRow(w *tabwriter.Writer, name string, service Service) {
154184 }
155185 profilesStr := strings .Join (profiles , ", " )
156186
157- if longFormat {
187+ if commandFormat {
188+ // Command format: NAME + executable command with env vars inline
158189 var commandStr string
159- var serverType string
160190
161191 // Check if this is a remote server
162192 if IsRemoteServer (service ) {
163- // For remote servers, show the URL and indicate it's remote
193+ // For remote servers, just show the URL
194+ commandStr = service .Command
195+ } else {
196+ // Build env var prefix for the command
197+ var envPrefix string
198+ if ! IsRemoteServer (service ) && len (service .Environment ) > 0 {
199+ var envParts []string
200+ // Sort keys for consistent output
201+ var keys []string
202+ for key := range service .Environment {
203+ keys = append (keys , key )
204+ }
205+ sort .Strings (keys )
206+ for _ , key := range keys {
207+ value := service .Environment [key ]
208+ expandedValue := expandEnvVars (value , envVars )
209+ envParts = append (envParts , fmt .Sprintf ("%s=%s" , key , shellQuote (expandedValue )))
210+ }
211+ envPrefix = strings .Join (envParts , " " ) + " "
212+ }
213+
214+ // Get the container tool from config, default to "docker"
215+ containerTool := "docker"
216+ configDir := getConfigDir ()
217+ configPath := filepath .Join (configDir , "config.json" )
218+
219+ if _ , err := os .Stat (configPath ); err == nil {
220+ data , err := os .ReadFile (configPath )
221+ if err == nil {
222+ var config CLIConfig
223+ if err := json .Unmarshal (data , & config ); err == nil && config .ContainerTool != "" {
224+ containerTool = config .ContainerTool
225+ }
226+ }
227+ }
228+
229+ if service .Image != "" {
230+ // For image-based servers, show the container run command format
231+ commandStr = fmt .Sprintf ("%s run -i --rm" , containerTool )
232+
233+ // Add environment variables as -e flags
234+ var keys []string
235+ for key := range service .Environment {
236+ keys = append (keys , key )
237+ }
238+ sort .Strings (keys )
239+ for _ , key := range keys {
240+ value := service .Environment [key ]
241+ expandedValue := expandEnvVars (value , envVars )
242+ commandStr += fmt .Sprintf (" -e %s=%s" , key , shellQuote (expandedValue ))
243+ }
244+
245+ // Add the image name
246+ commandStr += fmt .Sprintf (" %s" , service .Image )
247+ } else {
248+ // For command-based servers, prepend env vars and expand command
249+ expandedCommand := expandEnvVars (service .Command , envVars )
250+ commandStr = envPrefix + expandedCommand
251+ }
252+ }
253+
254+ fmt .Fprintf (w , "%s\t %s\n " , name , commandStr )
255+ } else if longFormat {
256+ var commandStr string
257+
258+ // Check if this is a remote server
259+ if IsRemoteServer (service ) {
260+ // For remote servers, show the URL
164261 commandStr = service .Command
165- serverType = "remote"
166262 } else {
167263 // Get the container tool from config, default to "docker"
168264 containerTool := "docker"
@@ -190,25 +286,23 @@ func printServerRow(w *tabwriter.Writer, name string, service Service) {
190286
191287 // Add the image name
192288 commandStr += fmt .Sprintf (" %s" , service .Image )
193- serverType = "container"
194289 } else {
195290 // For command-based servers, show the command
196291 commandStr = service .Command
197- serverType = "local"
198292 }
199293 }
200294
201295 // Get environment variables (only for local servers, remote servers use OAuth)
202- var envVars []string
296+ var envVarsDisplay []string
203297 if ! IsRemoteServer (service ) {
204298 for key := range service .Environment {
205- envVars = append (envVars , key )
299+ envVarsDisplay = append (envVarsDisplay , key )
206300 }
207301 }
208- envVarsStr := strings .Join (envVars , ", " )
302+ sort .Strings (envVarsDisplay )
303+ envVarsStr := strings .Join (envVarsDisplay , ", " )
209304
210- // Include server type in the output to distinguish remote vs local servers
211- fmt .Fprintf (w , "%s\t %s\t %s (%s)\t %s\n " , name , profilesStr , commandStr , serverType , envVarsStr )
305+ fmt .Fprintf (w , "%s\t %s\t %s\t %s\n " , name , profilesStr , commandStr , envVarsStr )
212306 } else {
213307 // Simple format with just name and profiles
214308 fmt .Fprintf (w , "%s\t %s\n " , name , profilesStr )
0 commit comments