Harden MCP OAuth callback handling (#40691)

## Why

MCP servers can share an OAuth callback URL. Without a validated issuer or a
server-specific callback path, an authorization response could be associated
with the wrong server.

## What changed

- Use stable callbacks when authorization metadata advertises issuer-bound
  responses, and validate the returned issuer before exchanging the code.
- Retain server-specific callback IDs for providers without issuer support,
  including fallback to the global or default callback for legacy registered
  clients.
- Persist registered callback URLs for MCP servers and plugins, and insert the
  active listener port into portless loopback redirects.

## Testing

Add coverage for issuer validation, callback-mode discovery, registered and
legacy clients, plugin OAuth, CLI persistence, and loopback listener ports.

GitOrigin-RevId: 2878c92e237fc17fd3def0bd2e1cce3e104a3db8
This commit is contained in:
stevenlee-oai authored and copyberry committed 2026-08-25 20:26:23 +00:00
1 parent 32ce703a80
commit 9be8d6e1c3
28 files changed
+1096 -127

No files matched your search

+1
View File
@@ -7830,6 +7830,7 @@ async fn replace_mcp_servers_streamable_http_serializes_oauth_resource() -> anyh
scopes: None,
oauth: Some(McpServerOAuthConfig {
client_id: Some("eci-prd-pub-codex-123".to_string()),
callback_url: None,
callback_port: None,
}),
oauth_resource: Some("https://resource.example.com".to_string()),
@@ -155,6 +155,9 @@ fn serialize_mcp_server_table(config: &McpServerConfig) -> TomlTable {
{
oauth_table["client_id"] = value(client_id.clone());
}
if let Some(callback_url) = &oauth.callback_url {
oauth_table["callback_url"] = value(callback_url.clone());
}
if let Some(callback_port) = oauth.callback_port {
oauth_table["callback_port"] = value(i64::from(callback_port));
}
+2
View File
@@ -1087,6 +1087,7 @@ fn blocking_replace_mcp_servers_round_trips() {
scopes: None,
oauth: Some(McpServerOAuthConfig {
client_id: Some("eci-prd-pub-codex-123".to_string()),
callback_url: Some("http://127.0.0.1/callback/example".to_string()),
callback_port: Some(9876),
}),
oauth_resource: Some("https://resource.example.com".to_string()),
@@ -1116,6 +1117,7 @@ Z-Header = \"z\"
[mcp_servers.http.oauth]
client_id = \"eci-prd-pub-codex-123\"
callback_url = \"http://127.0.0.1/callback/example\"
callback_port = 9876
[mcp_servers.stdio]