5
5
"context"
6
6
"encoding/json"
7
7
"errors"
8
+ "net/url"
8
9
"os"
9
10
"path/filepath"
10
11
"slices"
@@ -361,7 +362,7 @@ func (r *RootCmd) mcpServer() *serpent.Command {
361
362
},
362
363
Short : "Start the Coder MCP server." ,
363
364
Middleware : serpent .Chain (
364
- r .InitClient (client ),
365
+ r .TryInitClient (client ),
365
366
),
366
367
Options : []serpent.Option {
367
368
{
@@ -396,19 +397,38 @@ func mcpServerHandler(inv *serpent.Invocation, client *codersdk.Client, instruct
396
397
397
398
fs := afero .NewOsFs ()
398
399
399
- me , err := client .User (ctx , codersdk .Me )
400
- if err != nil {
401
- cliui .Errorf (inv .Stderr , "Failed to log in to the Coder deployment." )
402
- cliui .Errorf (inv .Stderr , "Please check your URL and credentials." )
403
- cliui .Errorf (inv .Stderr , "Tip: Run `coder whoami` to check your credentials." )
404
- return err
405
- }
406
400
cliui .Infof (inv .Stderr , "Starting MCP server" )
407
- cliui .Infof (inv .Stderr , "User : %s" , me .Username )
408
- cliui .Infof (inv .Stderr , "URL : %s" , client .URL )
409
- cliui .Infof (inv .Stderr , "Instructions : %q" , instructions )
401
+
402
+ // Check authentication status
403
+ var username string
404
+
405
+ // Check authentication status first
406
+ if client != nil && client .URL != nil && client .SessionToken () != "" {
407
+ // Try to validate the client
408
+ me , err := client .User (ctx , codersdk .Me )
409
+ if err == nil {
410
+ username = me .Username
411
+ cliui .Infof (inv .Stderr , "Authentication : Successful" )
412
+ cliui .Infof (inv .Stderr , "User : %s" , username )
413
+ } else {
414
+ // Authentication failed but we have a client URL
415
+ cliui .Warnf (inv .Stderr , "Authentication : Failed (%s)" , err )
416
+ cliui .Warnf (inv .Stderr , "Some tools that require authentication will not be available." )
417
+ }
418
+ } else {
419
+ cliui .Infof (inv .Stderr , "Authentication : None" )
420
+ }
421
+
422
+ // Display URL separately from authentication status
423
+ if client != nil && client .URL != nil {
424
+ cliui .Infof (inv .Stderr , "URL : %s" , client .URL .String ())
425
+ } else {
426
+ cliui .Infof (inv .Stderr , "URL : Not configured" )
427
+ }
428
+
429
+ cliui .Infof (inv .Stderr , "Instructions : %q" , instructions )
410
430
if len (allowedTools ) > 0 {
411
- cliui .Infof (inv .Stderr , "Allowed Tools : %v" , allowedTools )
431
+ cliui .Infof (inv .Stderr , "Allowed Tools : %v" , allowedTools )
412
432
}
413
433
cliui .Infof (inv .Stderr , "Press Ctrl+C to stop the server" )
414
434
@@ -431,13 +451,33 @@ func mcpServerHandler(inv *serpent.Invocation, client *codersdk.Client, instruct
431
451
// Get the workspace agent token from the environment.
432
452
toolOpts := make ([]func (* toolsdk.Deps ), 0 )
433
453
var hasAgentClient bool
434
- if agentToken , err := getAgentToken (fs ); err == nil && agentToken != "" {
435
- hasAgentClient = true
436
- agentClient := agentsdk .New (client .URL )
437
- agentClient .SetSessionToken (agentToken )
438
- toolOpts = append (toolOpts , toolsdk .WithAgentClient (agentClient ))
454
+
455
+ var agentURL * url.URL
456
+ if client != nil && client .URL != nil {
457
+ agentURL = client .URL
458
+ } else if agntURL , err := getAgentURL (); err == nil {
459
+ agentURL = agntURL
460
+ }
461
+
462
+ // First check if we have a valid client URL, which is required for agent client
463
+ if agentURL == nil {
464
+ cliui .Infof (inv .Stderr , "Agent URL : Not configured" )
439
465
} else {
440
- cliui .Warnf (inv .Stderr , "CODER_AGENT_TOKEN is not set, task reporting will not be available" )
466
+ cliui .Infof (inv .Stderr , "Agent URL : %s" , agentURL .String ())
467
+ agentToken , err := getAgentToken (fs )
468
+ if err != nil || agentToken == "" {
469
+ cliui .Warnf (inv .Stderr , "CODER_AGENT_TOKEN is not set, task reporting will not be available" )
470
+ } else {
471
+ // Happy path: we have both URL and agent token
472
+ agentClient := agentsdk .New (agentURL )
473
+ agentClient .SetSessionToken (agentToken )
474
+ toolOpts = append (toolOpts , toolsdk .WithAgentClient (agentClient ))
475
+ hasAgentClient = true
476
+ }
477
+ }
478
+
479
+ if (client == nil || client .URL == nil || client .SessionToken () == "" ) && ! hasAgentClient {
480
+ return xerrors .New (notLoggedInMessage )
441
481
}
442
482
443
483
if appStatusSlug != "" {
@@ -458,6 +498,13 @@ func mcpServerHandler(inv *serpent.Invocation, client *codersdk.Client, instruct
458
498
cliui .Warnf (inv .Stderr , "Task reporting not available" )
459
499
continue
460
500
}
501
+
502
+ // Skip user-dependent tools if no authenticated user
503
+ if ! tool .UserClientOptional && username == "" {
504
+ cliui .Warnf (inv .Stderr , "Tool %q requires authentication and will not be available" , tool .Tool .Name )
505
+ continue
506
+ }
507
+
461
508
if len (allowedTools ) == 0 || slices .ContainsFunc (allowedTools , func (t string ) bool {
462
509
return t == tool .Tool .Name
463
510
}) {
@@ -730,6 +777,15 @@ func getAgentToken(fs afero.Fs) (string, error) {
730
777
return string (bs ), nil
731
778
}
732
779
780
+ func getAgentURL () (* url.URL , error ) {
781
+ urlString , ok := os .LookupEnv ("CODER_AGENT_URL" )
782
+ if ! ok || urlString == "" {
783
+ return nil , xerrors .New ("CODEDR_AGENT_URL is empty" )
784
+ }
785
+
786
+ return url .Parse (urlString )
787
+ }
788
+
733
789
// mcpFromSDK adapts a toolsdk.Tool to go-mcp's server.ServerTool.
734
790
// It assumes that the tool responds with a valid JSON object.
735
791
func mcpFromSDK (sdkTool toolsdk.GenericTool , tb toolsdk.Deps ) server.ServerTool {
0 commit comments