feat(cli): make MCP server work without user authentication (#17688)

Part of #17649

---

# Allow MCP server to run without authentication

This PR enhances the MCP server to operate without requiring authentication, making it more flexible for environments where authentication isn't available or necessary. Key changes:

- Replaced `InitClient` with `TryInitClient` to allow the MCP server to start without credentials
- Added graceful handling when URL or authentication is missing
- Made authentication status visible in server logs
- Added logic to skip user-dependent tools when no authenticated user is present
- Made the `coder_report_task` tool available with just an agent token (no user token required)
- Added comprehensive tests to verify operation without authentication

These changes allow the MCP server to function in more environments while still using authentication when available, improving flexibility for CI/CD and other automated environments.
This commit is contained in:
Thomas Kosiewski
2025-05-07 21:53:06 +02:00
committed by GitHub
parent 6ac1bd807c
commit 29bce8d9e6
5 changed files with 253 additions and 23 deletions

View File

@ -571,6 +571,58 @@ func (r *RootCmd) InitClient(client *codersdk.Client) serpent.MiddlewareFunc {
}
}
// TryInitClient is similar to InitClient but doesn't error when credentials are missing.
// This allows commands to run without requiring authentication, but still use auth if available.
func (r *RootCmd) TryInitClient(client *codersdk.Client) serpent.MiddlewareFunc {
return func(next serpent.HandlerFunc) serpent.HandlerFunc {
return func(inv *serpent.Invocation) error {
conf := r.createConfig()
var err error
// Read the client URL stored on disk.
if r.clientURL == nil || r.clientURL.String() == "" {
rawURL, err := conf.URL().Read()
// If the configuration files are absent, just continue without URL
if err != nil {
// Continue with a nil or empty URL
if !os.IsNotExist(err) {
return err
}
} else {
r.clientURL, err = url.Parse(strings.TrimSpace(rawURL))
if err != nil {
return err
}
}
}
// Read the token stored on disk.
if r.token == "" {
r.token, err = conf.Session().Read()
// Even if there isn't a token, we don't care.
// Some API routes can be unauthenticated.
if err != nil && !os.IsNotExist(err) {
return err
}
}
// Only configure the client if we have a URL
if r.clientURL != nil && r.clientURL.String() != "" {
err = r.configureClient(inv.Context(), client, r.clientURL, inv)
if err != nil {
return err
}
client.SetSessionToken(r.token)
if r.debugHTTP {
client.PlainLogger = os.Stderr
client.SetLogBodies(true)
}
client.DisableDirectConnections = r.disableDirect
}
return next(inv)
}
}
}
// HeaderTransport creates a new transport that executes `--header-command`
// if it is set to add headers for all outbound requests.
func (r *RootCmd) HeaderTransport(ctx context.Context, serverURL *url.URL) (*codersdk.HeaderTransport, error) {