From 579a6be3c8623d32cab2164ee5cc6e9bedc3a07a Mon Sep 17 00:00:00 2001 From: RameshReddy Adutla Date: Wed, 4 Mar 2026 21:21:36 +0000 Subject: [PATCH] Include client_id in client_credentials token request body ClientCredentialsOAuthProvider._exchange_token_client_credentials() was missing client_id in the token_data dict. Per RFC 6749 Section 2.3.1, when using client_secret_post authentication, both client_id and client_secret must be in the request body. The prepare_token_auth() method only adds client_secret, so client_id was never sent. This caused authentication failures when using client_secret_post with OAuth providers that require client_id in the body. Fixes modelcontextprotocol#2128 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/mcp/client/auth/extensions/client_credentials.py | 1 + tests/client/auth/extensions/test_client_credentials.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/mcp/client/auth/extensions/client_credentials.py b/src/mcp/client/auth/extensions/client_credentials.py index cb6dafb40..c59c4626b 100644 --- a/src/mcp/client/auth/extensions/client_credentials.py +++ b/src/mcp/client/auth/extensions/client_credentials.py @@ -90,6 +90,7 @@ async def _exchange_token_client_credentials(self) -> httpx.Request: """Build token exchange request for client_credentials grant.""" token_data: dict[str, Any] = { "grant_type": "client_credentials", + "client_id": self._fixed_client_info.client_id, } headers: dict[str, str] = {"Content-Type": "application/x-www-form-urlencoded"} diff --git a/tests/client/auth/extensions/test_client_credentials.py b/tests/client/auth/extensions/test_client_credentials.py index 0003b1679..dfa68faff 100644 --- a/tests/client/auth/extensions/test_client_credentials.py +++ b/tests/client/auth/extensions/test_client_credentials.py @@ -249,6 +249,7 @@ async def test_exchange_token_client_credentials(self, mock_storage: MockTokenSt content = urllib.parse.unquote_plus(request.content.decode()) assert "grant_type=client_credentials" in content + assert "client_id=test-client-id" in content assert "scope=read write" in content assert "resource=https://api.example.com/v1/mcp" in content @@ -272,6 +273,7 @@ async def test_exchange_token_without_scopes(self, mock_storage: MockTokenStorag content = urllib.parse.unquote_plus(request.content.decode()) assert "grant_type=client_credentials" in content + assert "client_id=test-client-id" in content assert "scope=" not in content assert "resource=" not in content