55 "context"
66 "encoding/json"
77 "errors"
8+ "net/url"
89 "os"
910 "path/filepath"
1011 "slices"
@@ -361,7 +362,7 @@ func (r *RootCmd) mcpServer() *serpent.Command {
361362 },
362363 Short : "Start the Coder MCP server." ,
363364 Middleware : serpent .Chain (
364- r .InitClient (client ),
365+ r .TryInitClient (client ),
365366 ),
366367 Options : []serpent.Option {
367368 {
@@ -396,19 +397,38 @@ func mcpServerHandler(inv *serpent.Invocation, client *codersdk.Client, instruct
396397
397398 fs := afero .NewOsFs ()
398399
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- }
406400 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 )
410430 if len (allowedTools ) > 0 {
411- cliui .Infof (inv .Stderr , "Allowed Tools : %v" , allowedTools )
431+ cliui .Infof (inv .Stderr , "Allowed Tools : %v" , allowedTools )
412432 }
413433 cliui .Infof (inv .Stderr , "Press Ctrl+C to stop the server" )
414434
@@ -431,13 +451,33 @@ func mcpServerHandler(inv *serpent.Invocation, client *codersdk.Client, instruct
431451 // Get the workspace agent token from the environment.
432452 toolOpts := make ([]func (* toolsdk.Deps ), 0 )
433453 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" )
439465 } 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 )
441481 }
442482
443483 if appStatusSlug != "" {
@@ -458,6 +498,13 @@ func mcpServerHandler(inv *serpent.Invocation, client *codersdk.Client, instruct
458498 cliui .Warnf (inv .Stderr , "Task reporting not available" )
459499 continue
460500 }
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+
461508 if len (allowedTools ) == 0 || slices .ContainsFunc (allowedTools , func (t string ) bool {
462509 return t == tool .Tool .Name
463510 }) {
@@ -730,6 +777,15 @@ func getAgentToken(fs afero.Fs) (string, error) {
730777 return string (bs ), nil
731778}
732779
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+
733789// mcpFromSDK adapts a toolsdk.Tool to go-mcp's server.ServerTool.
734790// It assumes that the tool responds with a valid JSON object.
735791func mcpFromSDK (sdkTool toolsdk.GenericTool , tb toolsdk.Deps ) server.ServerTool {
0 commit comments