Skip to content

Conversation

@philschmid
Copy link
Owner

Summary

Continuation of #22 with improvements based on review feedback.

Key change from PR #22: CLI now NEVER opens browser - always returns auth URL for AI agents.

What's Different from #22

Feature PR #22 This PR
Browser behavior Opens browser for single server Never opens browser
allowInteractiveAuth Configurable option Removed entirely
Port selection Fixed ports with fallback Random port by default
Error handling Interactive OAuth flow Always returns auth URL

Changes

  • Removed allowInteractiveAuth entirely - CLI is designed for AI agents
  • redirectToAuthorization() captures auth URL - never opens browser
  • AuthRequiredError includes authorization URL for immediate action
  • Callback server runs in background (5 min timeout) - CLI returns immediately
  • Random port by default - avoids conflicts with multiple OAuth servers
  • Comprehensive OAuth configuration docs - options table, three example scenarios

Previous OAuth commits in this branch (from #22)

  • feat: add OAuth support for HTTP MCP servers
  • fix: OAuth race condition and concurrent auth handling
  • feat: add OAuth callback port fallback and pretty HTML pages
  • fix: detect and invalidate stale OAuth client registrations
  • refactor: split oauth.ts into logical modules

Testing

All 232 tests pass, lint clean.


cc @attehuhtakangas - would appreciate if you could test this version! The main difference is that it never tries to open a browser, which is better suited for AI agent usage.

attehuhtakangas and others added 6 commits January 27, 2026 22:59
- Add McpCliOAuthProvider implementing OAuthClientProvider interface
- File-based token storage in ~/.mcp-cli/{tokens,clients,verifiers}
- Support authorization_code and client_credentials grant types
- Auto-create OAuth provider for all HTTP servers (enables server-initiated OAuth)
- Handle OAuth callback with local HTTP server on configurable port
- Cross-platform browser opening for authorization flow
- Detect OAuth errors from UnauthorizedError and invalid_token responses

This enables MCP servers like Linear that require OAuth authentication.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Start callback server BEFORE opening browser to fix race condition
  where browser redirects before server is ready
- Add allowInteractiveAuth option to disable OAuth prompts when
  listing multiple servers (prevents multiple browsers opening)
- Show helpful "requires authentication" message for unauthenticated
  servers when listing, with command to authenticate individually
- Export AuthRequiredError and ConnectOptions from client module

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add port fallback mechanism: tries 80 → 8080 → 3000 → 8095 → random
- Port 80 as default with standard URL format (http://localhost/callback)
- Add pretty styled HTML pages for success/error callbacks
- Add callbackPorts config option for custom port fallback list
- Pre-start callback server to determine actual port before auth flow
- Add comprehensive tests for new port fallback features

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When the callback server port changes between sessions (e.g., port 3000
was used during registration but port 8080 is available now), the OAuth
authorization would fail with "Invalid redirect_uri" because the server
expects the originally registered redirect_uri.

Changes:
- clientInformation() now validates stored redirect_uris match current
  redirectUrl, invalidating stale registrations that would cause errors
- redirectToAuthorization() reuses pre-started callback server instead
  of starting a new one, ensuring consistent port usage throughout the
  OAuth flow

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Split the 850-line oauth.ts into smaller, focused modules:
- types.ts: Interfaces (OAuthConfig, OAuthCallbackResult) and constants
- storage.ts: File storage utilities for tokens, clients, verifiers
- browser.ts: Cross-platform browser opening utility
- callback-server.ts: HTTP callback server with HTML templates
- provider.ts: Main McpCliOAuthProvider class
- index.ts: Re-exports for backwards compatibility

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
CLI NEVER opens browser - always returns auth URL for AI agents.

Key changes:
- Removed allowInteractiveAuth option entirely (CLI is for AI agents)
- redirectToAuthorization() now captures auth URL, never opens browser
- AuthRequiredError includes authorization URL for immediate action
- Callback server runs in background (5 min timeout) - CLI returns immediately
- List command shows working servers + auth URLs for servers needing login
- Random port by default to avoid conflicts with multiple OAuth servers
- Added comprehensive OAuth configuration docs to README

This builds on the previous OAuth commits in this branch.
@attehuhtakangas
Copy link

attehuhtakangas commented Feb 9, 2026

@philschmid it doesn't work, the callback server is stopped before it starts to listen. Example output:

mcp-cli -c /Users/atte/.config/mcp/mcp_servers.json datadog
Error [SERVER_CONNECTION_FAILED]: Failed to connect to server "datadog"
  Details: [AUTH REQUIRED] datadog
  Authenticate at: https://mcp.datadoghq.com/api/unstable/mcp-server/authorize?response_type=code&client_id=mcp_1234567&code_challenge=[OMITTED]&code_challenge_method=S256&redirect_uri=http%3A%2F%2Flocalhost%3A54723%2Fcallback&resource=https%3A%2F%2Fmcp.datadoghq.com%2F
  Callback server running in background (5 min timeout).
  After authenticating, confirm "done" and retry this command.
  Suggestion: Connection timed out. Check network connectivity and server availability

Since this is in your PR and branch now, here's my Claude's plan to fix it in case it's helpful:

Claude's plan
Fix: OAuth callback server dies immediately on process exit                         
                                                  
 Context                                  
                                     
 When running mcp-cli datadog, the CLI detects OAuth is required, starts a callback server, prints the auth URL, then immediately calls process.exit() — killing the callback server before it can receive
 the OAuth redirect. The auth URL points to a dead callback server.

 The root cause is in info.ts (and call.ts): they catch AuthRequiredError as a generic error, wrap it in serverConnectionError(), and call process.exit(). The callback server lives in the same process and
  dies with it.

 A secondary issue: AuthRequiredError's message contains "5 min timeout", which triggers the misleading "Connection timed out" suggestion in serverConnectionError().

 Fix

 Instead of throwing AuthRequiredError and exiting, make connectToServer() wait for the OAuth callback when a waitForAuth option is set. The info and call commands pass this option; list keeps the current
  non-blocking behavior.

 The MCP SDK provides transport.finishAuth(code) exactly for this: it exchanges the authorization code for tokens and saves them.

 1. src/client.ts — Add waitForAuth option and blocking auth flow

 - Change ConnectOptions from Record<string, never> to { waitForAuth?: boolean }
 - In the catch block inside connectToServer() where isOAuthNeeded() is true:
   - If options.waitForAuth is true:
       i. Print auth URL + "Waiting for authentication..." to stderr
     ii. Call authProvider.waitForCallback() to block until code arrives
     iii. Call transport.finishAuth(code) to exchange code for tokens
     iv. Create fresh Client + createHttpTransport() and connect (tokens now on disk)
     v. Return the new connected client
   - If waitForAuth is false/undefined: throw AuthRequiredError (current behavior for list)

 2. src/commands/info.ts — Handle auth and pass waitForAuth: true

 - Import AuthRequiredError
 - Pass { waitForAuth: true } to getConnection()
 - In the catch block, detect AuthRequiredError separately and show auth-specific error (not the misleading "Connection timed out" suggestion)

 3. src/commands/call.ts — Pass waitForAuth: true

 - Import AuthRequiredError
 - Pass { waitForAuth: true } to getConnection()
 - Handle AuthRequiredError with auth-specific error messaging

 4. src/errors.ts — Better suggestion for auth-related errors

 - In serverConnectionError(), check for "AUTH REQUIRED" or "OAuth callback" before the generic "timeout" check, so auth errors get an appropriate suggestion instead of "Connection timed out"

 Files to modify

 - src/client.ts — Core fix: blocking auth flow in connectToServer()
 - src/commands/info.ts — Pass waitForAuth: true, handle AuthRequiredError
 - src/commands/call.ts — Pass waitForAuth: true, handle AuthRequiredError
 - src/errors.ts — Fix misleading timeout suggestion for auth errors
Details

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants