feat(mcp): Add OAuth 2.0 authentication support for MCP servers#2842
feat(mcp): Add OAuth 2.0 authentication support for MCP servers#2842amitksingh1490 wants to merge 12 commits intomainfrom
Conversation
- Add McpOAuthConfig and McpOAuthSetting to configure OAuth per server - Implement mcp_auth, mcp_logout, mcp_auth_status API methods - Add McpCredentialStore for persistent token storage with 0o600 permissions - Implement McpTokenStorage adapter for rmcp's CredentialStore trait - Support auto-detection of OAuth via 401 responses, explicit config, or disabled - Add CLI commands: `mcp login <name>` and `mcp logout <name|all>` - Integrate rmcp's OAuth state machine with PKCE, dynamic registration, metadata discovery - Open browser automatically and run local callback server for authorization flow
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
- Add `allow_interactive` parameter to control OAuth flow behavior - Prevent browser popups during normal MCP connections - Add automatic MCP reload after auth/login/logout operations - Update refresh_cache to avoid eager connections during reload
| use std::os::unix::fs::PermissionsExt; | ||
| let mut perms = std::fs::metadata(&path)?.permissions(); | ||
| perms.set_mode(0o600); | ||
| std::fs::set_permissions(&path, perms)?; |
There was a problem hiding this comment.
Blocking I/O in async function: Uses synchronous std::fs operations (std::fs::metadata and std::fs::set_permissions) inside an async function, which will block the async runtime.
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
use tokio::fs as async_fs;
let metadata = async_fs::metadata(&path).await?;
let mut perms = metadata.permissions();
perms.set_mode(0o600);
async_fs::set_permissions(&path, perms).await?;
}| use std::os::unix::fs::PermissionsExt; | |
| let mut perms = std::fs::metadata(&path)?.permissions(); | |
| perms.set_mode(0o600); | |
| std::fs::set_permissions(&path, perms)?; | |
| #[cfg(unix)] | |
| { | |
| use std::os::unix::fs::PermissionsExt; | |
| use tokio::fs as async_fs; | |
| let metadata = async_fs::metadata(&path).await?; | |
| let mut perms = metadata.permissions(); | |
| perms.set_mode(0o600); | |
| async_fs::set_permissions(&path, perms).await?; | |
| } | |
Spotted by Graphite
Is this helpful? React 👍 or 👎 to let us know.
| let mut auth_manager = AuthorizationManager::new(server_url) | ||
| .await | ||
| .map_err(|e| anyhow::anyhow!("Failed to create OAuth manager: {}", e))?; | ||
|
|
||
| auth_manager.set_credential_store(credential_store); |
There was a problem hiding this comment.
Dead code: Creates an AuthorizationManager with credential store but never uses it. The OAuth flow is immediately started with a fresh OAuthState on line 615, making this initialization wasteful and misleading.
// Remove these unused lines:
// let mut auth_manager = AuthorizationManager::new(server_url)
// .await
// .map_err(|e| anyhow::anyhow!("Failed to create OAuth manager: {}", e))?;
// auth_manager.set_credential_store(credential_store);Spotted by Graphite
Is this helpful? React 👍 or 👎 to let us know.
| { | ||
| use rmcp::transport::auth::CredentialStore; | ||
| let save_store = McpTokenStorage::new(http.url.clone(), self.environment.clone()); | ||
| let stored = rmcp::transport::auth::StoredCredentials { | ||
| client_id: credentials.0, | ||
| token_response: credentials.1, | ||
| }; | ||
| let _ = save_store.save(stored).await; | ||
| } |
There was a problem hiding this comment.
Credentials save error is silently ignored. If credential persistence fails after successful OAuth, the user will complete authentication but have to re-authenticate on next use since tokens aren't saved.
// Fix: Propagate the error instead of ignoring it
save_store
.save(stored)
.await
.map_err(|e| anyhow::anyhow!("Failed to save credentials: {}", e))?;| { | |
| use rmcp::transport::auth::CredentialStore; | |
| let save_store = McpTokenStorage::new(http.url.clone(), self.environment.clone()); | |
| let stored = rmcp::transport::auth::StoredCredentials { | |
| client_id: credentials.0, | |
| token_response: credentials.1, | |
| }; | |
| let _ = save_store.save(stored).await; | |
| } | |
| { | |
| use rmcp::transport::auth::CredentialStore; | |
| let save_store = McpTokenStorage::new(http.url.clone(), self.environment.clone()); | |
| let stored = rmcp::transport::auth::StoredCredentials { | |
| client_id: credentials.0, | |
| token_response: credentials.1, | |
| }; | |
| save_store.save(stored).await | |
| .map_err(|e| anyhow::anyhow!("Failed to save credentials: {}", e))?; | |
| } | |
Spotted by Graphite
Is this helpful? React 👍 or 👎 to let us know.
Summary
Add OAuth 2.0 authentication support for MCP (Model Context Protocol) servers, enabling secure delegated access through standardized OAuth flows including PKCE, dynamic client registration, and automatic metadata discovery.
Context
MCP servers often require authentication to access protected resources. This change adds full OAuth 2.0 support to Forge, allowing users to authenticate with MCP servers that require OAuth (e.g., Slack, GitHub, Google APIs). The implementation follows the OAuth 2.0 standard with PKCE for security, supports automatic discovery of authorization server metadata (RFC 8414), and includes dynamic client registration when servers don't require pre-registered clients.
Changes
OAuth Configuration: Added
McpOAuthConfigandMcpOAuthSettingtypes to configure OAuth per-server with support for:Credential Storage: Implemented
McpCredentialStorefor secure token persistence:McpTokenStorageadapter for rmcp'sCredentialStoretraitAPI Methods: Added three new API methods:
mcp_auth(server_url): Initiates OAuth flow with browser auto-open and local callback servermcp_logout(server_url): Removes stored credentials for a specific server or all serversmcp_auth_status(server_url): Checks authentication statusCLI Commands: Added new CLI commands:
forge mcp login <name>: Authenticate with a specific MCP serverforge mcp logout <name|all>: Logout from a specific server or all serversOAuth Flow Integration: Integrated rmcp's OAuth state machine:
Key Implementation Details
http://127.0.0.1:8765/mcp/callbackas the redirect URIUse Cases
Testing
Links
closes #2777