WWW-Authenticate parsing matches a field name as a substring of another auth-param

Open Beginner friendly
#3,009 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
78/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Quiet
Tech stack
python

Research direction

Start in mcp/client/auth/utils.py at extract_field_from_www_auth and inspect the existing test_extract_field_from_www_auth_* parametrized tests. Reproduce the cases with decoy parameter names such as error_scope and custom_scope, then verify the parser only returns complete auth-param names and that resource_metadata discovery ignores decoys.

Written by the indexing model from the issue text.

Description

auth bug P2 v2
Describe the bug

Ran into this wiring up an OAuth-protected MCP client. When a 401 WWW-Authenticate challenge carries several auth-params, extract_field_from_www_auth (mcp/client/auth/utils.py) matches the requested field name as a substring of another param's name, so a different param shadows the real value.

The pattern is rf'{field_name}=(?:"([^"]+)"|([^\s,]+))' searched with re.search, with no boundary before field_name.

To reproduce
import httpx
from mcp.client.auth.utils import extract_field_from_www_auth, extract_resource_metadata_from_www_auth

def r(h):
    return httpx.Response(401, headers={"WWW-Authenticate": h},
                          request=httpx.Request("GET", "https://api.example.com/"))

# A real `scope` is present, but `error_scope` shadows it:
print(extract_field_from_www_auth(r('Bearer error_scope="decoy", scope="read write"'), "scope"))
# -> 'decoy'   (expected 'read write')

# Only a decoy param exists; no real `scope`:
print(extract_field_from_www_auth(r('Bearer custom_scope="leaked"'), "scope"))
# -> 'leaked'  (expected None)

# Same for resource_metadata, which drives discovery URL selection:
print(extract_resource_metadata_from_www_auth(r('Bearer x_resource_metadata="https://decoy.example.com"')))
# -> 'https://decoy.example.com'  (expected None)
Expected behavior

The field name should match only as a complete auth-param name (at the header start or after a whitespace/comma separator), so scope doesn't match inside error_scope. This matters most for resource_metadata, since the client uses it to pick the protected-resource-metadata discovery URL — reading it from the wrong param points discovery at the wrong place.

Environment
  • mcp main (reproduces on v1.x as well)

I have a small fix (anchor the param name to the header start or a separator) plus regression cases added to the existing test_extract_field_from_www_auth_* parametrized tests, and can open a PR if this looks right.

Reviewed by AI

Dominant language
Python
Stars
24.3k
Forks
4k
Avg merge
1d 19m
Merged PRs (30d)
29

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from modelcontextprotocol/python-sdk

All issues in modelcontextprotocol/python-sdk

Similar issues

More Python issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.