Skip to content

Commit cff3925

Browse files
add targeted MCP OAuth2 diagnostics for redirect URI mismatches (#1830)
Added focused auth logging in MCP OAuth flows to make redirect registration failures diagnosable without exposing secrets. This captures DCR request/response context (including normalized returned redirect URIs), authorize-request inputs, and callback bind details; true redirect mismatches remain warning while forensic details are logged at debug. ## By Submitting this PR I confirm: - I am familiar with the [Contributing Guidelines](https://github.com/NVIDIA/NeMo-Agent-Toolkit/blob/develop/docs/source/resources/contributing/index.md). - We require that all contributors "sign-off" on their commits. This certifies that the contribution is your original work, or you have rights to submit it under the same license, or a compatible license. - Any contribution which contains commits that are not Signed-Off will not be accepted. - When the PR is ready for review, new or existing tests cover these changes. - When the PR is ready for review, the documentation is up to date with these changes. ## Summary by CodeRabbit * **Improvements** * Improved debugging across OAuth flows with structured logging of authorization URL components and registration responses. * Added validation and warnings for redirect URI mismatches and clearer logging of client registration and authorization inputs. Authors: - Anuradha Karuppiah (https://github.com/AnuradhaKaruppiah) Approvers: - Will Killian (https://github.com/willkill07) URL: #1830
1 parent 727f3a6 commit cff3925

2 files changed

Lines changed: 48 additions & 1 deletion

File tree

packages/nvidia_nat_mcp/src/nat/plugins/mcp/auth/auth_flow_handler.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
import logging
1818
import secrets
1919
import webbrowser
20+
from urllib.parse import parse_qs
21+
from urllib.parse import urlparse
2022

2123
import pkce
2224
from authlib.integrations.httpx_client import AsyncOAuth2Client
@@ -92,7 +94,6 @@ async def _handle_oauth2_auth_code_flow(self, cfg: OAuth2AuthCodeFlowProviderCon
9294
logger.info("Starting MCP OAuth2 authorization code flow")
9395

9496
# Extract and validate host and port from redirect_uri for callback server
95-
from urllib.parse import urlparse
9697
parsed_uri = urlparse(str(cfg.redirect_uri))
9798

9899
# Validate scheme/host and choose a safe non-privileged bind port
@@ -133,13 +134,30 @@ async def _handle_oauth2_auth_code_flow(self, cfg: OAuth2AuthCodeFlowProviderCon
133134
flow_state.challenge = challenge
134135
logger.debug("PKCE enabled for MCP authentication")
135136

137+
logger.debug("MCP OAuth authorize URL input: authorization_url=%s redirect_uri=%s",
138+
cfg.authorization_url,
139+
cfg.redirect_uri)
136140
auth_url, _ = client.create_authorization_url(
137141
cfg.authorization_url,
138142
state=state,
139143
code_verifier=flow_state.verifier if cfg.use_pkce else None,
140144
code_challenge=flow_state.challenge if cfg.use_pkce else None,
141145
**(cfg.authorization_kwargs or {})
142146
)
147+
parsed_auth_url = urlparse(auth_url)
148+
parsed_auth_params = parse_qs(parsed_auth_url.query)
149+
logger.debug(
150+
"MCP OAuth authorize URL params: endpoint=%s://%s%s client_id=%s redirect_uri=%s scope=%s resource=%s "
151+
"state_prefix=%s",
152+
parsed_auth_url.scheme,
153+
parsed_auth_url.netloc,
154+
parsed_auth_url.path,
155+
parsed_auth_params.get("client_id", [None])[0],
156+
parsed_auth_params.get("redirect_uri", [None])[0],
157+
parsed_auth_params.get("scope", [None])[0],
158+
parsed_auth_params.get("resource", [None])[0],
159+
state[:8],
160+
)
143161

144162
async with self._server_lock:
145163
if self._redirect_app is None:

packages/nvidia_nat_mcp/src/nat/plugins/mcp/auth/auth_provider.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,14 @@ async def register(self, endpoints: OAuth2Endpoints, scopes: list[str] | None) -
258258
client_name=self.config.client_name or None,
259259
)
260260
payload = metadata.model_dump(by_alias=True, mode="json", exclude_none=True)
261+
logger.debug(
262+
"MCP DCR request: registration_url=%s server_url=%s redirect_uri=%s scopes=%s auth_method=%s",
263+
registration_url,
264+
self.config.server_url,
265+
self.config.redirect_uri,
266+
scopes,
267+
getattr(self.config, "token_endpoint_auth_method", None) or "client_secret_post",
268+
)
261269

262270
async with httpx.AsyncClient(timeout=30.0) as client:
263271
resp = await client.post(
@@ -279,6 +287,18 @@ async def register(self, endpoints: OAuth2Endpoints, scopes: list[str] | None) -
279287
if not info.client_id:
280288
raise RuntimeError("No client_id received from registration")
281289

290+
returned_redirect_uris = getattr(info, "redirect_uris", None)
291+
returned_redirect_uris_str = ([str(uri) for uri in returned_redirect_uris] if returned_redirect_uris else None)
292+
logger.info("MCP DCR response: client_id=%s returned_redirect_uris=%s",
293+
info.client_id,
294+
returned_redirect_uris_str)
295+
if returned_redirect_uris_str and str(self.config.redirect_uri) not in returned_redirect_uris_str:
296+
logger.warning(
297+
"MCP DCR redirect mismatch: requested_redirect_uri=%s returned_redirect_uris=%s client_id=%s",
298+
self.config.redirect_uri,
299+
returned_redirect_uris_str,
300+
info.client_id,
301+
)
282302
logger.info("Successfully registered OAuth2 client: %s", info.client_id)
283303
return OAuth2Credentials(client_id=info.client_id, client_secret=info.client_secret)
284304

@@ -420,6 +440,15 @@ async def _nat_oauth2_authenticate(self, user_id: str | None = None) -> AuthResu
420440
scopes=scopes,
421441
use_pkce=bool(self.config.use_pkce),
422442
authorization_kwargs={"resource": str(self.config.server_url)})
443+
logger.info(
444+
"MCP OAuth authorize request inputs: authorization_url=%s client_id=%s redirect_uri=%s "
445+
"resource=%s scopes=%s",
446+
oauth2_config.authorization_url,
447+
oauth2_config.client_id,
448+
oauth2_config.redirect_uri,
449+
oauth2_config.authorization_kwargs.get("resource") if oauth2_config.authorization_kwargs else None,
450+
oauth2_config.scopes,
451+
)
423452
self._auth_code_provider = OAuth2AuthCodeFlowProvider(oauth2_config, token_storage=self._token_storage)
424453

425454
# Use MCP-specific authentication method if available

0 commit comments

Comments
 (0)