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},
362363Short :"Start the Coder MCP server." ,
363364Middleware :serpent .Chain (
364- r .InitClient (client ),
365+ r .TryInitClient (client ),
365366),
366367Options : []serpent.Option {
367368{
@@ -396,19 +397,38 @@ func mcpServerHandler(inv *serpent.Invocation, client *codersdk.Client, instruct
396397
397398fs := 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- }
406400cliui .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 )
410430if len (allowedTools )> 0 {
411- cliui .Infof (inv .Stderr ,"Allowed Tools : %v" ,allowedTools )
431+ cliui .Infof (inv .Stderr ,"Allowed Tools : %v" ,allowedTools )
412432}
413433cliui .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.
432452toolOpts := make ([]func (* toolsdk.Deps ),0 )
433453var 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
443483if appStatusSlug != "" {
@@ -458,6 +498,13 @@ func mcpServerHandler(inv *serpent.Invocation, client *codersdk.Client, instruct
458498cliui .Warnf (inv .Stderr ,"Task reporting not available" )
459499continue
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+
461508if len (allowedTools )== 0 || slices .ContainsFunc (allowedTools ,func (t string )bool {
462509return t == tool .Tool .Name
463510}) {
@@ -730,6 +777,15 @@ func getAgentToken(fs afero.Fs) (string, error) {
730777return 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 {