Skip to content

Adding Client Credentials & Token Exchange Grant Types to Auth #882

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 36 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
833a105
Add client credentials OAuth grant
SoldierSacha Jun 3, 2025
66c7e67
Merge pull request #1 from sacha-development-stuff/codex/add-support-…
SoldierSacha Jun 3, 2025
813168a
Allow client credentials in dynamic registration
SoldierSacha Jun 3, 2025
dbbc6ce
Merge pull request #2 from sacha-development-stuff/codex/review-and-i…
SoldierSacha Jun 3, 2025
3f2a351
Refactor OAuth helpers
SoldierSacha Jun 3, 2025
62c729d
Merge pull request #3 from sacha-development-stuff/codex/review-imple…
SoldierSacha Jun 3, 2025
5212ce0
clean up code
SoldierSacha Jun 3, 2025
d9c751f
linting
SoldierSacha Jun 4, 2025
7848e68
Fix tests and pyright errors
SoldierSacha Jun 4, 2025
e325b95
Merge pull request
SoldierSacha Jun 4, 2025
3a45cf8
work
SoldierSacha Jun 4, 2025
2132cde
test
SoldierSacha Jun 4, 2025
5c87fb3
test
SoldierSacha Jun 4, 2025
103e201
test
SoldierSacha Jun 4, 2025
ad59c92
Fix async fixture usage in OAuth tests
SoldierSacha Jun 4, 2025
e18e606
Merge pull request #5 from sacha-development-stuff/codex/fix-attribut…
SoldierSacha Jun 4, 2025
49fa6c2
Fix resumption token updates
SoldierSacha Jun 4, 2025
b46aac4
Merge pull request
SoldierSacha Jun 4, 2025
2daea3f
Add OAuth token exchange support
SoldierSacha Jun 10, 2025
94850e7
implement-rfc-8693-token-exchange-in-mcp-sdk
SoldierSacha Jun 10, 2025
627eebd
work
SoldierSacha Jun 10, 2025
beeb244
Merge branch 'main' into main
SoldierSacha Jun 10, 2025
e92e61d
docs: document token-exchange support
SoldierSacha Jun 10, 2025
5976e77
docs
SoldierSacha Jun 10, 2025
bde2448
test: update expectations for token-exchange
SoldierSacha Jun 10, 2025
a98f33f
Merge pull request #9 from sacha-development-stuff/codex/fix-token-ex…
SoldierSacha Jun 10, 2025
b3b0509
Fix pyright token type errors
SoldierSacha Jun 10, 2025
a3edbeb
fix-argument-type-and-abstract-class-errors
SoldierSacha Jun 10, 2025
9b5ef4d
work
SoldierSacha Jun 10, 2025
a0d24ca
Strip whitespace from SSE resumption token
SoldierSacha Jun 10, 2025
7e02ddd
fix-test_streamablehttp_client_resumption-failure
SoldierSacha Jun 10, 2025
d04d17c
Merge branch 'main' into main
SoldierSacha Jun 13, 2025
2d6c062
merge with recent branch
SoldierSacha Jun 13, 2025
02597a2
feat: support combined client creds and token exchange
SoldierSacha Jun 14, 2025
e717dbe
adding token exchange + client credentials as a valid registration gr…
SoldierSacha Jun 14, 2025
1f23248
merge with recent branch
SoldierSacha Jun 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -814,7 +814,11 @@ async def main():
The SDK includes [authorization support](https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization) for connecting to protected MCP servers:

```python
from mcp.client.auth import OAuthClientProvider, TokenStorage
from mcp.client.auth import (
OAuthClientProvider,
TokenExchangeProvider,
TokenStorage,
)
from mcp.client.session import ClientSession
from mcp.client.streamable_http import streamablehttp_client
from mcp.shared.auth import OAuthClientInformationFull, OAuthClientMetadata, OAuthToken
Expand Down Expand Up @@ -851,6 +855,24 @@ async def main():
callback_handler=lambda: ("auth_code", None),
)

# For machine-to-machine scenarios, use ClientCredentialsProvider
# instead of OAuthClientProvider.

# If you already have a user token from another provider, you can
# exchange it for an MCP token using the token_exchange grant
# implemented by TokenExchangeProvider.
token_exchange_auth = TokenExchangeProvider(
server_url="https://api.example.com",
client_metadata=OAuthClientMetadata(
client_name="My Client",
redirect_uris=["http://localhost:3000/callback"],
grant_types=["client_credentials", "token_exchange"],
response_types=["code"],
),
storage=CustomTokenStorage(),
subject_token_supplier=lambda: "user_token",
)

# Use with streamable HTTP client
async with streamablehttp_client(
"https://api.example.com/mcp", auth=oauth_auth
Expand Down
4 changes: 4 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
The Python SDK exposes the entire `mcp` package for use in your own projects.
It includes an OAuth server implementation with support for the RFC 8693
`token_exchange` grant type.

::: mcp
4 changes: 4 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@
This is the MCP Server implementation in Python.

It only contains the [API Reference](api.md) for the time being.

The built-in OAuth server supports the RFC 8693 `token_exchange` grant type,
allowing clients to exchange user tokens from external providers for MCP
access tokens.
30 changes: 30 additions & 0 deletions examples/servers/simple-auth/mcp_simple_auth/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,36 @@ async def exchange_refresh_token(
"""Exchange refresh token"""
raise NotImplementedError("Not supported")

async def exchange_token(
self,
client: OAuthClientInformationFull,
subject_token: str,
subject_token_type: str,
actor_token: str | None,
actor_token_type: str | None,
scope: list[str] | None,
audience: str | None,
resource: str | None,
) -> OAuthToken:
"""Exchange an external token for an MCP access token."""
raise NotImplementedError("Token exchange is not supported")

async def exchange_client_credentials(self, client: OAuthClientInformationFull, scopes: list[str]) -> OAuthToken:
"""Exchange client credentials for an access token."""
token = f"mcp_{secrets.token_hex(32)}"
self.tokens[token] = AccessToken(
token=token,
client_id=client.client_id,
scopes=scopes,
expires_at=int(time.time()) + 3600,
)
return OAuthToken(
access_token=token,
token_type="Bearer",
expires_in=3600,
scope=" ".join(scopes),
)

async def revoke_token(self, token: str, token_type_hint: str | None = None) -> None:
"""Revoke a token."""
if token in self.tokens:
Expand Down
Loading
Loading