Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 81 additions & 14 deletions docs/mcp_oauth_servers.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,26 @@ This feature adds automatic OAuth 2.0 authentication support for remote MCP serv
## Platform Support

**✅ Web Platform**: Full OAuth support with popup-based authentication flow
**❌ Mobile/Desktop**: OAuth authentication is **web-only** due to browser security requirements
**✅ Desktop (Windows/macOS/Linux)**: Full OAuth support with system browser-based authentication flow
**❌ Mobile (iOS/Android)**: OAuth authentication is not yet supported on mobile platforms

On non-web platforms:
### Platform-Specific Implementation

#### Web Platform
- Opens OAuth authorization in a popup window
- Uses cross-origin messaging for callback handling
- Returns to the same web application seamlessly

#### Desktop Platform (Windows, macOS, Linux)
- Opens OAuth authorization in the system's default browser
- Runs a local HTTP server on `localhost:8080` to receive the callback
- Displays success/error page in browser after authentication
- Automatically closes the browser window (or prompts user to close)

#### Mobile Platform
- OAuth discovery still works (detects requirements)
- OAuth authentication throws `UnsupportedError`
- Fallback to manual configuration or other auth methods
- OAuth authentication is not yet implemented
- Future enhancement planned for mobile OAuth support

## Tested OAuth Providers

Expand All @@ -35,9 +49,10 @@ On non-web platforms:
- Falls back to public client mode if no client registration

2. **Authentication Phase**:
- Opens OAuth authorization popup
- Handles PKCE code challenge/verifier generation
- Processes OAuth callback with state validation
- **Web**: Opens OAuth authorization in a popup window
- **Desktop**: Opens OAuth authorization in the system's default browser
- Handles PKCE code challenge/verifier generation for security
- Processes OAuth callback with state validation to prevent CSRF attacks
- Exchanges authorization code for access token

3. **Usage Phase**:
Expand All @@ -47,10 +62,11 @@ On non-web platforms:

## Architecture

### Web Platform
```
┌─────────────────┐ ┌────────────────────┐ ┌─────────────────┐
│ MCP Server │ │ OAuth Discovery │ │ OAuth Handler │
│ │◄──►│ │◄──►│
│ │◄──►│ │◄──►│ (Web/Popup)
│ /.well-known/ │ │ RFC 8414 Compliant │ │ PKCE + Popup │
│ oauth-auth... │ │ │ │ Cross-origin │
└─────────────────┘ └────────────────────┘ └─────────────────┘
Expand All @@ -65,30 +81,81 @@ On non-web platforms:
└────────────────────┘
```

### Desktop Platform (Windows/macOS/Linux)
```
┌─────────────────┐ ┌────────────────────┐ ┌─────────────────┐
│ MCP Server │ │ OAuth Discovery │ │ OAuth Handler │
│ │◄──►│ │◄──►│ (Desktop) │
│ /.well-known/ │ │ RFC 8414 Compliant │ │ PKCE + Browser │
│ oauth-auth... │ │ │ │ Local Server │
└─────────────────┘ └────────────────────┘ └─────────────────┘
│ │
▼ ▼
┌────────────────────┐ ┌─────────────────┐
│ MCP Client │ │ System Browser │
│ │ │ │
│ Bearer Token Auth │ │ localhost:8080 │
│ StreamableClient │ │ Callback Server │
│ SSEClient │ │ │
└────────────────────┘ └─────────────────┘
```

## Usage

### Web Platform
1. Go to **Settings → MCP Servers**
2. Enter an OAuth-protected MCP server URL (e.g., `https://mcp.notion.com/mcp`)
2. Enter an OAuth-protected MCP server URL (e.g., `https://mcp.atlassian.com/v1/mcp`)
3. Click **Add Server** - OAuth requirements are detected automatically
4. If OAuth is required, you'll be prompted to authenticate
5. Complete the OAuth flow in the popup window
6. The server will be ready to use with automatic token authentication

### Desktop Platform (Windows/macOS/Linux)
1. Go to **Settings → MCP Servers**
2. Enter an OAuth-protected MCP server URL (e.g., `https://mcp.atlassian.com/v1/mcp`)
3. Click **Add Server** - OAuth requirements are detected automatically
4. If OAuth is required, you'll be prompted to authenticate
5. Your default browser will open to the OAuth provider's authorization page
6. After granting permission, you'll see a success page in the browser
7. Return to ChatMCP - the server will be ready to use with automatic token authentication

## Security Features

- **PKCE Protection**: Prevents authorization code interception attacks
- **State Parameter Validation**: Prevents CSRF attacks
- **Origin Validation**: Ensures callbacks come from expected sources
- **Browser Extension Filtering**: Ignores interference from development tools
- **Origin Validation**: Ensures callbacks come from expected sources (web)
- **Browser Extension Filtering**: Ignores interference from development tools (web)
- **Localhost Binding**: Local callback server binds only to localhost for security (desktop)
- **Token Expiry Handling**: Automatic refresh before expiration

## Future Enhancements

- Mobile/Desktop OAuth support via external browser
- Additional OAuth flows (device code, etc.)
- ✅ ~~Desktop OAuth support via external browser~~ (Implemented in this version)
- Mobile OAuth support (iOS/Android)
- Additional OAuth flows (device code, client credentials, etc.)
- OAuth provider-specific optimizations
- Enhanced error handling and user feedback
- Configurable callback server port for desktop

---

This implementation follows OAuth 2.0 security best practices and modern web standards for a robust, user-friendly authentication experience.
## Implementation Notes

### Platform-Specific OAuth Handlers

The OAuth implementation uses Dart's conditional imports to provide platform-specific handlers:

- **Web Platform** (`oauth_web.dart`): Uses popup windows and `postMessage` API for callback handling
- **Desktop Platform** (`oauth_io.dart`): Uses `url_launcher` to open system browser and runs a local HTTP server on `localhost:8080` for callbacks
- **Stub** (`oauth_stub.dart`): Placeholder for unsupported platforms (currently mobile)

### File Structure

- `lib/utils/oauth_discovery.dart`: Platform-agnostic OAuth discovery service (RFC 8414, RFC 7591)
- `lib/utils/oauth_web.dart`: Web-specific OAuth handler
- `lib/utils/oauth_io.dart`: Desktop-specific OAuth handler (Windows/macOS/Linux)
- `lib/utils/oauth_stub.dart`: Stub for unsupported platforms
- `lib/provider/mcp_server_provider.dart`: OAuth integration with MCP server management
- `web/oauth_callback.html`: OAuth callback page for web platform

This implementation follows OAuth 2.0 security best practices and modern web standards for a robust, user-friendly authentication experience across multiple platforms.
28 changes: 13 additions & 15 deletions lib/provider/mcp_server_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import 'package:chatmcp/utils/storage_manager.dart';
import '../mcp/client/mcp_client_interface.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../mcp/models/server.dart';
import '../utils/oauth_web.dart' if (dart.library.io) '../utils/oauth_stub.dart';
import '../utils/oauth_web.dart' if (dart.library.io) '../utils/oauth_io.dart';
import '../utils/oauth_discovery.dart';

var defaultInMemoryServers = [
Expand Down Expand Up @@ -435,12 +435,18 @@ class McpServerProvider extends ChangeNotifier {

// OAuth-related methods

/// Gets the default redirect URI for the current platform
String _getDefaultRedirectUri() {
if (kIsWeb) {
return '${Uri.base.origin}/oauth_callback.html';
} else {
// For desktop platforms, use localhost with a default port
return 'http://localhost:8080/oauth/callback';
}
}

/// Discovers OAuth configuration for a server URL automatically
Future<OAuthDiscoveryResult> discoverOAuthForServer(String serverUrl) async {
if (!kIsWeb) {
return OAuthDiscoveryResult(requiresOAuth: false);
}

try {
Logger.root.info('Discovering OAuth for server: $serverUrl');
final result = await OAuthDiscoveryService.discoverOAuth(serverUrl);
Expand All @@ -463,7 +469,7 @@ class McpServerProvider extends ChangeNotifier {

/// Automatically configures and authenticates a server with discovered OAuth
Future<bool> autoAuthenticateServer(String serverName, OAuthDiscoveryResult oauthConfig) async {
if (!kIsWeb || !oauthConfig.requiresOAuth) {
if (!oauthConfig.requiresOAuth) {
return false;
}

Expand All @@ -473,7 +479,7 @@ class McpServerProvider extends ChangeNotifier {
// Use discovered client ID or null for public clients (like Notion MCP)
String? clientId = oauthConfig.clientId;
String scope = oauthConfig.scope ?? 'read write';
String redirectUri = oauthConfig.redirectUri ?? '${Uri.base.origin}/oauth_callback.html';
String redirectUri = oauthConfig.redirectUri ?? _getDefaultRedirectUri();

Logger.root.info('Using clientId: $clientId, scope: $scope, redirectUri: $redirectUri');

Expand Down Expand Up @@ -564,10 +570,6 @@ class McpServerProvider extends ChangeNotifier {
throw Exception('OAuth not enabled for server: $serverName');
}

if (!kIsWeb) {
throw Exception('OAuth is only supported on web platform');
}

// Extract OAuth parameters with debugging
final authorizationUrl = oauth['authorization_url'] as String? ?? '';
final clientId = oauth['client_id'] as String? ?? '';
Expand Down Expand Up @@ -640,10 +642,6 @@ class McpServerProvider extends ChangeNotifier {
throw Exception('No refresh token available for server: $serverName');
}

if (!kIsWeb) {
throw Exception('OAuth is only supported on web platform');
}

// Refresh token
final tokenResult = await WebOAuthHandler.refreshToken(
tokenUrl: oauth['token_url'] as String,
Expand Down
12 changes: 9 additions & 3 deletions lib/utils/oauth_discovery.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:logging/logging.dart';
import 'package:flutter/foundation.dart' show kIsWeb;

/// Result of OAuth discovery for an MCP server
class OAuthDiscoveryResult {
Expand Down Expand Up @@ -222,9 +223,14 @@ class OAuthDiscoveryService {

/// Generate appropriate redirect URI for current platform
static String _generateRedirectUri() {
// Get current page URL and construct redirect URI
final currentUrl = Uri.base;
return '${currentUrl.origin}/oauth_callback.html';
if (kIsWeb) {
// Web platform: use current page origin
final currentUrl = Uri.base;
return '${currentUrl.origin}/oauth_callback.html';
} else {
// Desktop platforms: use localhost with default port
return 'http://localhost:8080/oauth/callback';
}
}

/// Try dynamic client registration (RFC 7591)
Expand Down
Loading