Skip to content

Commit 7c6aea1

Browse files
g97iulio1609Copilot
andcommitted
fix: add coverage for client_secret_post None client_id branch
Add test for edge case where client_id is None in client_secret_post auth method. This ensures 100% branch coverage on oauth2.py (previously missing the 210->212 branch). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent e841bba commit 7c6aea1

File tree

2 files changed

+38
-1
lines changed

2 files changed

+38
-1
lines changed

src/mcp/client/auth/oauth2.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ def prepare_token_auth(
207207
data = {k: v for k, v in data.items() if k != "client_secret"}
208208
elif auth_method == "client_secret_post" and self.client_info.client_secret:
209209
# Include client_id and client_secret in request body (RFC 6749 §2.3.1)
210-
if self.client_info.client_id:
210+
if self.client_info.client_id is not None:
211211
data["client_id"] = self.client_info.client_id
212212
data["client_secret"] = self.client_info.client_secret
213213
# For auth_method == "none", don't add any client_secret

tests/client/auth/extensions/test_client_credentials.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,43 @@ async def test_exchange_token_client_secret_post_includes_client_id(self, mock_s
280280
# Should NOT have Basic auth header
281281
assert "Authorization" not in request.headers
282282

283+
@pytest.mark.anyio
284+
async def test_exchange_token_client_secret_post_without_client_id(self, mock_storage: MockTokenStorage):
285+
"""Test that client_secret_post omits client_id from body when it is None."""
286+
provider = ClientCredentialsOAuthProvider(
287+
server_url="https://api.example.com/v1/mcp",
288+
storage=mock_storage,
289+
client_id="placeholder",
290+
client_secret="test-client-secret",
291+
token_endpoint_auth_method="client_secret_post",
292+
scopes="read write",
293+
)
294+
await provider._initialize()
295+
provider.context.oauth_metadata = OAuthMetadata(
296+
issuer=AnyHttpUrl("https://api.example.com"),
297+
authorization_endpoint=AnyHttpUrl("https://api.example.com/authorize"),
298+
token_endpoint=AnyHttpUrl("https://api.example.com/token"),
299+
)
300+
provider.context.protocol_version = "2025-06-18"
301+
# Override client_info to have client_id=None (edge case)
302+
provider.context.client_info = OAuthClientInformationFull(
303+
redirect_uris=None,
304+
client_id=None,
305+
client_secret="test-client-secret",
306+
grant_types=["client_credentials"],
307+
token_endpoint_auth_method="client_secret_post",
308+
scope="read write",
309+
)
310+
311+
request = await provider._perform_authorization()
312+
313+
content = urllib.parse.unquote_plus(request.content.decode())
314+
assert "grant_type=client_credentials" in content
315+
assert "client_secret=test-client-secret" in content
316+
# client_id should NOT be in body since it's None
317+
assert "client_id=" not in content
318+
assert "Authorization" not in request.headers
319+
283320
@pytest.mark.anyio
284321
async def test_exchange_token_without_scopes(self, mock_storage: MockTokenStorage):
285322
"""Test token exchange without scopes."""

0 commit comments

Comments
 (0)