charmbracelet/crush · warning

interactive OAuth authorization required

Error message

interactive OAuth authorization required

What it means

ErrInteractiveAuthRequired is returned by the MCP OAuth handler's Authorize when a server needs browser-based interactive authorization but the current context forbids it. Background connections (e.g. at startup) deliberately withhold the interactive permission so a missing token surfaces as a needs-auth state instead of silently opening a browser and blocking initialization.

Source

Thrown at internal/oauth/mcp/handler.go:32

	"strings"
	"sync"
	"time"

	"github.com/charmbracelet/crush/internal/oauth"
	"github.com/charmbracelet/crush/internal/oauth/callback"
	"github.com/modelcontextprotocol/go-sdk/auth"
	"github.com/modelcontextprotocol/go-sdk/oauthex"
	"github.com/pkg/browser"
	"golang.org/x/oauth2"
)

// ErrInteractiveAuthRequired is returned by Authorize when a server needs
// interactive (browser) authorization but the current context does not
// permit it. Background connections such as startup deliberately withhold
// permission so a failed or missing token surfaces as a needs-auth state
// instead of silently opening a browser and blocking initialization. The
// user then triggers the interactive flow explicitly.
var ErrInteractiveAuthRequired = errors.New("interactive OAuth authorization required")

// interactiveKey marks a context as permitting the interactive browser flow.
type interactiveKey struct{}

// WithInteractive returns a context that permits the interactive browser
// authorization flow. Only user-initiated authentication should use it.
func WithInteractive(ctx context.Context) context.Context {
	return context.WithValue(ctx, interactiveKey{}, true)
}

// IsInteractive reports whether ctx permits the interactive browser flow.
func IsInteractive(ctx context.Context) bool {
	v, _ := ctx.Value(interactiveKey{}).(bool)
	return v
}

// callbackPath is the path the authorization server redirects back to. It
// is part of the registered redirect URI, so it must not change without

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Detect the sentinel with errors.Is(err, mcp.ErrInteractiveAuthRequired) and surface a 'needs authentication' state to the user.
  2. Re-run Authorize with a context from mcp.WithInteractive(ctx) when the user explicitly initiates login.
  3. Pre-authenticate the MCP server interactively before relying on background connections.

Example fix

// before
if err := handler.Authorize(ctx); err != nil { return err }
// after
if err := handler.Authorize(ctx); err != nil {
    if errors.Is(err, mcp.ErrInteractiveAuthRequired) {
        ui.PromptMcpLogin(server) // user-triggered
        ctx = mcp.WithInteractive(ctx)
        return handler.Authorize(ctx)
    }
    return err
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Only authorize interactively from user-initiated paths:
// ctx := mcp.WithInteractive(ctx) before calling Authorize in UI code.

Type guard

func isInteractiveAuthRequired(err error) bool {
    return errors.Is(err, mcp.ErrInteractiveAuthRequired)
}

Try / catch

if err := handler.Authorize(ctx); err != nil {
    if errors.Is(err, mcp.ErrInteractiveAuthRequired) {
        return ErrNeedsUserAuth // surface needs-auth state
    }
    return err
}

Prevention

When it happens

Trigger: Calling Authorize on an MCP OAuth handler without a context created via WithInteractive when the server has no valid token and must do a fresh browser flow.

Common situations: Crush starts up, connects to an MCP server whose stored token is expired/absent, and the background authorize path refuses to open a browser; the user must run the interactive auth flow explicitly (e.g. via /mcp auth command).

Related errors


AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29). Data as JSON: /api/errors/a0dd87472e8d3f22. Report an issue: GitHub.