mirror of
https://github.com/open-webui/open-webui.git
synced 2026-09-27 01:40:33 -04:00
fix: keep requested MCP OAuth scope when DCR response omits it (#30384)
MCP tool servers using OAuth 2.1 with dynamic client registration authorized without any scope when the authorization server left scope out of its registration response, which RFC 7591 allows (Atlassian and Notion do). Consent completed and the tool showed as connected, but the issued token lacked the scopes the resource requires, so every tool call was refused. Discovered scopes and the custom OAuth Scopes field were both affected. The stored client now falls back to the scope sent in the registration request when the response has none. A scope the server does return is kept as is. Connections registered before this fix already have a null scope stored. The protected resource metadata recovery that static-credential clients already use now also runs for dynamically registered clients, so those connections pick up the discovered scopes on the next load without registering again. Fixes #29967
This commit is contained in:
@@ -264,7 +264,7 @@ from open_webui.utils.oauth import (
|
||||
encrypt_data,
|
||||
get_oauth_client_info_with_dynamic_client_registration,
|
||||
get_oauth_client_info_with_static_credentials,
|
||||
recover_static_oauth_client_metadata,
|
||||
recover_oauth_client_metadata,
|
||||
resolve_oauth_client_info,
|
||||
)
|
||||
from open_webui.utils.plugin import install_tool_and_function_dependencies
|
||||
@@ -636,9 +636,7 @@ async def initialize_runtime_config(app: FastAPI):
|
||||
if server_id and auth_type in ('oauth_2.1', 'oauth_2.1_static'):
|
||||
try:
|
||||
oauth_client_info = resolve_oauth_client_info(tool_server_connection)
|
||||
oauth_client_info = await recover_static_oauth_client_metadata(
|
||||
tool_server_connection, oauth_client_info
|
||||
)
|
||||
oauth_client_info = await recover_oauth_client_metadata(tool_server_connection, oauth_client_info)
|
||||
oauth_client_info = apply_connection_oauth_options(tool_server_connection, oauth_client_info)
|
||||
app.state.oauth_client_manager.add_client(
|
||||
f'mcp:{server_id}',
|
||||
|
||||
@@ -22,7 +22,7 @@ from open_webui.utils.oauth import (
|
||||
get_discovery_urls,
|
||||
get_oauth_client_info_with_dynamic_client_registration,
|
||||
get_oauth_client_info_with_static_credentials,
|
||||
recover_static_oauth_client_metadata,
|
||||
recover_oauth_client_metadata,
|
||||
resolve_oauth_client_info,
|
||||
)
|
||||
from open_webui.utils.tools import (
|
||||
@@ -273,7 +273,7 @@ async def set_tool_servers_config(
|
||||
if auth_type in ('oauth_2.1', 'oauth_2.1_static') and server_id:
|
||||
try:
|
||||
oauth_client_info = resolve_oauth_client_info(connection)
|
||||
oauth_client_info = await recover_static_oauth_client_metadata(connection, oauth_client_info)
|
||||
oauth_client_info = await recover_oauth_client_metadata(connection, oauth_client_info)
|
||||
oauth_client_info = apply_connection_oauth_options(connection, oauth_client_info)
|
||||
request.app.state.oauth_client_manager.add_client(
|
||||
f'{server_type}:{server_id}',
|
||||
|
||||
@@ -612,6 +612,8 @@ async def get_oauth_client_info_with_dynamic_client_registration(
|
||||
oauth_client_info = OAuthClientInformationFull.model_validate(
|
||||
{
|
||||
**registration_response_json,
|
||||
# RFC 7591: the server may omit scope; keep the requested one.
|
||||
'scope': registration_response_json.get('scope') or oauth_client_metadata.scope,
|
||||
'issuer': oauth_server_metadata_url,
|
||||
'server_metadata': oauth_server_metadata,
|
||||
'resource': resource,
|
||||
@@ -799,10 +801,7 @@ def build_oauth_request_params(client_info: OAuthClientInformationFull | None) -
|
||||
return params
|
||||
|
||||
|
||||
async def recover_static_oauth_client_metadata(connection: dict, oauth_client_info: dict) -> dict:
|
||||
if connection.get('auth_type') != 'oauth_2.1_static':
|
||||
return oauth_client_info
|
||||
|
||||
async def recover_oauth_client_metadata(connection: dict, oauth_client_info: dict) -> dict:
|
||||
if oauth_client_info.get('scope') and oauth_client_info.get('resource'):
|
||||
return oauth_client_info
|
||||
|
||||
@@ -813,13 +812,13 @@ async def recover_static_oauth_client_metadata(connection: dict, oauth_client_in
|
||||
try:
|
||||
resource_metadata = await get_protected_resource_metadata(server_url)
|
||||
except Exception as e:
|
||||
log.debug('Unable to recover static OAuth metadata for %s: %s', server_url, e)
|
||||
log.debug('Unable to recover OAuth metadata for %s: %s', server_url, e)
|
||||
return oauth_client_info
|
||||
|
||||
recovered = {**oauth_client_info}
|
||||
if not recovered.get('scope') and resource_metadata.scopes_supported:
|
||||
recovered['scope'] = ' '.join(resource_metadata.scopes_supported)
|
||||
log.info('Recovered static OAuth scopes for %s from protected resource metadata', server_url)
|
||||
log.info('Recovered OAuth scopes for %s from protected resource metadata', server_url)
|
||||
|
||||
if not recovered.get('resource') and resource_metadata.resource:
|
||||
recovered['resource'] = resource_metadata.resource
|
||||
@@ -916,7 +915,7 @@ class OAuthClientManager:
|
||||
|
||||
try:
|
||||
oauth_client_info = resolve_oauth_client_info(connection)
|
||||
oauth_client_info = await recover_static_oauth_client_metadata(connection, oauth_client_info)
|
||||
oauth_client_info = await recover_oauth_client_metadata(connection, oauth_client_info)
|
||||
oauth_client_info = apply_connection_oauth_options(connection, oauth_client_info)
|
||||
return self.add_client(expected_client_id, OAuthClientInformationFull(**oauth_client_info))['client']
|
||||
except InvalidToken:
|
||||
|
||||
Reference in New Issue
Block a user