Skip to content

feat(mcp): Add OAuth 2.0 authentication support for MCP servers#2842

Draft
amitksingh1490 wants to merge 12 commits intomainfrom
mcp-oauth
Draft

feat(mcp): Add OAuth 2.0 authentication support for MCP servers#2842
amitksingh1490 wants to merge 12 commits intomainfrom
mcp-oauth

Conversation

@amitksingh1490
Copy link
Copy Markdown
Contributor

@amitksingh1490 amitksingh1490 commented Apr 4, 2026

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 McpOAuthConfig and McpOAuthSetting types to configure OAuth per-server with support for:

    • Auto-detection via 401 responses
    • Explicit configuration with client_id, client_secret, scopes
    • Automatic discovery of auth_url/token_url from server metadata
    • Disabled mode for servers that don't need OAuth
  • Credential Storage: Implemented McpCredentialStore for secure token persistence:

    • Separate storage from LLM provider credentials
    • File permissions set to 0o600 (user read/write only)
    • Tokens bound to specific MCP server URLs
    • McpTokenStorage adapter for rmcp's CredentialStore trait
  • API Methods: Added three new API methods:

    • mcp_auth(server_url): Initiates OAuth flow with browser auto-open and local callback server
    • mcp_logout(server_url): Removes stored credentials for a specific server or all servers
    • mcp_auth_status(server_url): Checks authentication status
  • CLI Commands: Added new CLI commands:

    • forge mcp login <name>: Authenticate with a specific MCP server
    • forge mcp logout <name|all>: Logout from a specific server or all servers
  • OAuth Flow Integration: Integrated rmcp's OAuth state machine:

    • PKCE (Proof Key for Code Exchange) for secure authorization
    • Dynamic client registration (RFC 7591)
    • OAuth 2.0 Authorization Server Metadata discovery (RFC 8414)
    • Automatic browser opening and local callback server on port 8765

Key Implementation Details

  • OAuth flow runs a local HTTP server on http://127.0.0.1:8765/mcp/callback as the redirect URI
  • Credentials are stored in a JSON file separate from other Forge configuration
  • The implementation handles the complete OAuth dance: discovery → authorization → token exchange → storage
  • Automatic detection mode triggers OAuth flow when a 401 Unauthorized response is received

Use Cases

  • Slack MCP Server: Users can now authenticate with Slack's OAuth to access their workspaces
  • GitHub MCP Server: Secure access to GitHub APIs through OAuth rather than personal access tokens
  • Google APIs: Access Google services (Drive, Calendar, etc.) through standardized OAuth
  • Enterprise MCP Servers: Support for corporate OAuth providers with custom authorization servers

Testing

# Build with new OAuth features
cargo build

# Run unit tests for credential storage
cargo test -p forge_infra mcp_credentials

# Test OAuth configuration parsing
cargo test -p forge_domain mcp

# To test the actual OAuth flow (requires an MCP server with OAuth):
# 1. Configure an MCP server with OAuth settings in your forge.yaml
# 2. Run: cargo run -- mcp login <server-name>
# 3. Browser should open automatically for authorization

Links

closes #2777

- 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
@github-actions github-actions bot added the type: feature Brand new functionality, features, pages, workflows, endpoints, etc. label Apr 4, 2026
@amitksingh1490 amitksingh1490 changed the title feat(mcp): add OAuth authentication support for MCP servers feat(mcp): Add OAuth 2.0 authentication support for MCP servers Apr 4, 2026
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
amitksingh1490 and others added 5 commits April 4, 2026 23:52
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
@tusharmath tusharmath enabled auto-merge (squash) April 7, 2026 03:22
Comment on lines +84 to +87
use std::os::unix::fs::PermissionsExt;
let mut perms = std::fs::metadata(&path)?.permissions();
perms.set_mode(0o600);
std::fs::set_permissions(&path, perms)?;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?;
}
Suggested change
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

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

Comment on lines +608 to +612
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);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

Comment on lines +329 to +337
{
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;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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))?;
Suggested change
{
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

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

@amitksingh1490 amitksingh1490 disabled auto-merge April 7, 2026 03:43
@amitksingh1490 amitksingh1490 marked this pull request as draft April 7, 2026 04:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: feature Brand new functionality, features, pages, workflows, endpoints, etc.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: MCP OAuth / OpenID Integration

2 participants