modelcontextprotocol / modelcontextprotocol/python-sdk

OAuth: 403 responses without insufficient_scope incorrectly retry with same token

オープン
#1,602 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

auth P3
主要言語
Python
スター
24.3k
フォーク
4k
平均マージ
1日 1時間
マージ済み PR(30日)
31

説明

Summary

The OAuth client unconditionally retries all 403 responses, even when the error is not insufficient_scope. This causes an unnecessary retry attempt with the same token that will fail for the same reason.

Location

src/mcp/client/auth/oauth2.py, lines 662-681

The Bug

elif response.status_code == 403:
    error = self._extract_field_from_www_auth(response, "error")
    
    # Only performs step-up if error == "insufficient_scope"
    if error == "insufficient_scope":
        self._select_scopes(response)
        token_response = yield await self._perform_authorization()
        await self._handle_token_response(token_response)
    
    # BUG: Retries unconditionally, even when no new tokens were obtained
    self._add_auth_header(request)
    yield request

Lines 679-681 execute regardless of whether step-up authorization occurred, causing a retry with the same credentials.

Expected vs Actual Behavior

Scenario Expected Actual
403 with insufficient_scope Get new tokens → retry ✅ Correct
403 with different error (e.g., invalid_token) Raise error immediately ❌ Retries once with same token, then fails
403 with no error field Raise error immediately ❌ Retries once with same token, then fails

Impact

  • Wasted network round-trip: Client makes doomed retry request that will fail for the same reason
  • Poor error feedback: Delays error reporting by one request cycle
  • Spec non-compliance: MCP Authorization Spec implies retry only for insufficient_scope
  • Resource waste: Unnecessary load on server and client

Fix

Move the retry logic inside the if error == "insufficient_scope": block and raise an error otherwise:

elif response.status_code == 403:
    error = self._extract_field_from_www_auth(response, "error")
    
    if error == "insufficient_scope":
        try:
            self._select_scopes(response)
            token_response = yield await self._perform_authorization()
            await self._handle_token_response(token_response)
            
            # Retry with new tokens
            self._add_auth_header(request)
            yield request
        except Exception:
            logger.exception("OAuth flow error")
            raise
    else:
        # Permanent authorization failure - cannot be resolved by retry
        raise OAuthFlowError(
            f"Access forbidden: {error or 'insufficient permissions'}"
        )

References


Authored by Claude, reviewed by @maxisbey

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

調査の方向性

src/mcp/client/auth/oauth2.py の 662~681 行付近を読み、error フィールドの抽出後に 403 レスポンスがどのように処理されるかに注目してください。insufficient_scope、別のエラー、error フィールドがない場合の動作を確認してください。scope-challenge パスだけがリトライし、その他の 403 レスポンスは直ちに例外を発生させれば完了です。

索引モデルが issue の本文から書いたものです。

評価

技術スタック
python
領域
authentication
issue の種類
バグ
難易度
2/5
見積もり時間
1〜3時間
活発さ
停滞
明瞭さ
明確に書かれている
初心者へのやさしさ
55/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。