@@ -30,16 +30,20 @@ var (
3030 authLoginCmd = & cobra.Command {
3131 Use : "login" ,
3232 Short : "Manually authenticate with an OAuth-enabled server" ,
33- Long : `Manually trigger OAuth authentication flow for a specific upstream server.
33+ Long : `Manually trigger OAuth authentication flow for a specific upstream server or all servers .
3434This is useful when:
3535- A server requires OAuth but automatic attempts have been rate limited
3636- You want to authenticate proactively before using server tools
3737- OAuth tokens have expired and need refreshing
38+ - Multiple servers need re-authentication
3839
3940The command will open your default browser for OAuth authorization.
41+ Use --all to authenticate all servers that require OAuth (confirmation prompt unless --force is used).
4042
4143Examples:
4244 mcpproxy auth login --server=Sentry-2
45+ mcpproxy auth login --all
46+ mcpproxy auth login --all --force
4347 mcpproxy auth login --server=github-server --log-level=debug
4448 mcpproxy auth login --server=google-calendar --timeout=5m` ,
4549 RunE : runAuthLogin ,
@@ -82,6 +86,7 @@ Examples:
8286 authConfigPath string
8387 authTimeout time.Duration
8488 authAll bool
89+ authForce bool
8590)
8691
8792// GetAuthCommand returns the auth command for adding to the root command
@@ -96,7 +101,9 @@ func init() {
96101 authCmd .AddCommand (authLogoutCmd )
97102
98103 // Define flags for auth login command
99- authLoginCmd .Flags ().StringVarP (& authServerName , "server" , "s" , "" , "Server name to authenticate with (required)" )
104+ authLoginCmd .Flags ().StringVarP (& authServerName , "server" , "s" , "" , "Server name to authenticate with" )
105+ authLoginCmd .Flags ().BoolVar (& authAll , "all" , false , "Authenticate all servers that require OAuth" )
106+ authLoginCmd .Flags ().BoolVar (& authForce , "force" , false , "Skip confirmation prompt when using --all" )
100107 authLoginCmd .Flags ().StringVarP (& authLogLevel , "log-level" , "l" , "info" , "Log level (trace, debug, info, warn, error)" )
101108 authLoginCmd .Flags ().StringVarP (& authConfigPath , "config" , "c" , "" , "Path to MCP configuration file (default: ~/.mcpproxy/mcp_config.json)" )
102109 authLoginCmd .Flags ().DurationVar (& authTimeout , "timeout" , 5 * time .Minute , "Authentication timeout" )
@@ -114,12 +121,8 @@ func init() {
114121 authLogoutCmd .Flags ().DurationVar (& authTimeout , "timeout" , 30 * time .Second , "Logout timeout" )
115122
116123 // Mark required flags
117- err := authLoginCmd .MarkFlagRequired ("server" )
118- if err != nil {
119- panic (fmt .Sprintf ("Failed to mark server flag as required: %v" , err ))
120- }
121-
122- err = authLogoutCmd .MarkFlagRequired ("server" )
124+ // Note: auth login doesn't mark --server as required because --all can be used instead
125+ err := authLogoutCmd .MarkFlagRequired ("server" )
123126 if err != nil {
124127 panic (fmt .Sprintf ("Failed to mark server flag as required: %v" , err ))
125128 }
@@ -128,6 +131,12 @@ func init() {
128131 authLoginCmd .Example = ` # Authenticate with Sentry-2 server
129132 mcpproxy auth login --server=Sentry-2
130133
134+ # Authenticate all servers that require OAuth
135+ mcpproxy auth login --all
136+
137+ # Authenticate all servers without confirmation prompt
138+ mcpproxy auth login --all --force
139+
131140 # Authenticate with debug logging
132141 mcpproxy auth login --server=github-server --log-level=debug
133142
@@ -150,7 +159,18 @@ func init() {
150159 mcpproxy auth logout --server=github-server --log-level=debug`
151160}
152161
153- func runAuthLogin (_ * cobra.Command , _ []string ) error {
162+ func runAuthLogin (cmd * cobra.Command , _ []string ) error {
163+ // Validate flags: either --server or --all must be provided (but not both)
164+ if authAll && authServerName != "" {
165+ return fmt .Errorf ("cannot use both --server and --all flags together" )
166+ }
167+ if ! authAll && authServerName == "" {
168+ return fmt .Errorf ("either --server or --all flag is required" )
169+ }
170+
171+ // After validation, silence usage for operational errors
172+ cmd .SilenceUsage = true
173+
154174 ctx , cancel := context .WithTimeout (context .Background (), authTimeout )
155175 defer cancel ()
156176
@@ -160,6 +180,12 @@ func runAuthLogin(_ *cobra.Command, _ []string) error {
160180 return fmt .Errorf ("failed to load configuration: %w" , err )
161181 }
162182
183+ // Handle --all flag: authenticate all servers
184+ if authAll {
185+ return runAuthLoginAll (ctx , cfg .DataDir )
186+ }
187+
188+ // Handle single server authentication
163189 // Check if daemon is running and use client mode
164190 if shouldUseAuthDaemon (cfg .DataDir ) {
165191 return runAuthLoginClientMode (ctx , cfg .DataDir , authServerName )
@@ -169,6 +195,122 @@ func runAuthLogin(_ *cobra.Command, _ []string) error {
169195 return runAuthLoginStandalone (ctx , authServerName )
170196}
171197
198+ // runAuthLoginAll authenticates all servers that require OAuth authentication.
199+ func runAuthLoginAll (ctx context.Context , dataDir string ) error {
200+ // Auth login --all REQUIRES daemon
201+ if ! shouldUseAuthDaemon (dataDir ) {
202+ return fmt .Errorf ("auth login --all requires running daemon. Start with: mcpproxy serve" )
203+ }
204+
205+ socketPath := socket .DetectSocketPath (dataDir )
206+ logger , err := logs .SetupCommandLogger (false , authLogLevel , false , "" )
207+ if err != nil {
208+ return fmt .Errorf ("failed to create logger: %w" , err )
209+ }
210+ defer func () { _ = logger .Sync () }()
211+
212+ client := cliclient .NewClient (socketPath , logger .Sugar ())
213+
214+ // Ping daemon to verify connectivity
215+ pingCtx , pingCancel := context .WithTimeout (ctx , 2 * time .Second )
216+ defer pingCancel ()
217+ if err := client .Ping (pingCtx ); err != nil {
218+ return fmt .Errorf ("failed to connect to daemon: %w" , err )
219+ }
220+
221+ // Fetch all servers to find those that need OAuth authentication
222+ servers , err := client .GetServers (ctx )
223+ if err != nil {
224+ return fmt .Errorf ("failed to get servers from daemon: %w" , err )
225+ }
226+
227+ // Filter servers that need login (health.action == "login")
228+ var serversNeedingAuth []string
229+ for _ , srv := range servers {
230+ name , _ := srv ["name" ].(string )
231+ if health , ok := srv ["health" ].(map [string ]interface {}); ok && health != nil {
232+ if action , ok := health ["action" ].(string ); ok && action == "login" {
233+ serversNeedingAuth = append (serversNeedingAuth , name )
234+ }
235+ }
236+ }
237+
238+ if len (serversNeedingAuth ) == 0 {
239+ fmt .Println ("✅ No servers require authentication" )
240+ fmt .Println (" All OAuth-enabled servers are already authenticated." )
241+ return nil
242+ }
243+
244+ // Display servers that will be authenticated
245+ fmt .Println ("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" )
246+ fmt .Println ("🔐 Batch OAuth Authentication" )
247+ fmt .Println ("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" )
248+ fmt .Printf ("\n The following %d server(s) require authentication:\n " , len (serversNeedingAuth ))
249+ for i , name := range serversNeedingAuth {
250+ fmt .Printf (" %d. %s\n " , i + 1 , name )
251+ }
252+ fmt .Println ()
253+
254+ // Show warning about multiple browser tabs
255+ if len (serversNeedingAuth ) > 1 {
256+ fmt .Printf ("⚠️ This will open %d browser tabs for OAuth authorization.\n \n " , len (serversNeedingAuth ))
257+ }
258+
259+ // Prompt for confirmation unless --force is used
260+ if ! authForce {
261+ fmt .Print ("Do you want to continue? (yes/no): " )
262+ var response string
263+ if _ , err := fmt .Scanln (& response ); err != nil {
264+ return fmt .Errorf ("failed to read confirmation: %w" , err )
265+ }
266+ response = strings .TrimSpace (strings .ToLower (response ))
267+ if response != "yes" && response != "y" {
268+ fmt .Println ("\n ❌ Authentication cancelled" )
269+ return nil
270+ }
271+ fmt .Println ()
272+ }
273+
274+ // Authenticate each server
275+ var successful , failed int
276+ failedServers := make (map [string ]string ) // server name -> error message
277+
278+ fmt .Println ("Starting authentication..." )
279+ fmt .Println ()
280+
281+ for i , serverName := range serversNeedingAuth {
282+ fmt .Printf ("[%d/%d] Authenticating %s...\n " , i + 1 , len (serversNeedingAuth ), serverName )
283+
284+ if err := client .TriggerOAuthLogin (ctx , serverName ); err != nil {
285+ fmt .Printf (" ❌ Failed: %v\n " , err )
286+ failed ++
287+ failedServers [serverName ] = err .Error ()
288+ } else {
289+ fmt .Printf (" ✅ Success\n " )
290+ successful ++
291+ }
292+ fmt .Println ()
293+ }
294+
295+ // Display summary
296+ fmt .Println ("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" )
297+ fmt .Println ("📊 Authentication Summary" )
298+ fmt .Println ("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" )
299+ fmt .Printf ("Total: %d | Successful: %d | Failed: %d\n " , len (serversNeedingAuth ), successful , failed )
300+
301+ if failed > 0 {
302+ fmt .Println ("\n ❌ Failed servers:" )
303+ for serverName , errMsg := range failedServers {
304+ fmt .Printf (" • %s: %s\n " , serverName , errMsg )
305+ }
306+ fmt .Println ("\n Tip: Run 'mcpproxy auth login --server=<name>' to retry individual servers" )
307+ return fmt .Errorf ("%d server(s) failed to authenticate" , failed )
308+ }
309+
310+ fmt .Println ("\n ✅ All servers authenticated successfully!" )
311+ return nil
312+ }
313+
172314func runAuthStatus (_ * cobra.Command , _ []string ) error {
173315 ctx , cancel := context .WithTimeout (context .Background (), authTimeout )
174316 defer cancel ()
0 commit comments