-
Notifications
You must be signed in to change notification settings - Fork 45
First Party Support for Authentication (OAuth/APIKey) #70
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
base: main
Are you sure you want to change the base?
Conversation
82d5bda
to
cb3a9c0
Compare
@grll This is a relatively large PR so feel free to let me know if you'd like me to split it up in any way, and I am happy to help maintain the auth code over a longer period of time if you'd like 😄 |
Love this capability. From our use case, we would like to use |
… in functions to BearerAuthProvider.
@greedy52 - That seems like a valuable addition, considering that you'd need it to retrieve tokens from externally managed auth that expire quicker than the length of the agent "session". Now added with docs! |
This works great under smolagents for our internal use! Is this being actively reviewed? We'd much rather not have to maintain a fork for this functionality. |
@ukimme-work Great to hear! Although integrating it with something like Streamlit/Chainlit would be neat for quickly setting up a working UI for using agents with MCP, I'd think it'd be out of scope for this specific pull request which focuses on adding the core features for Auth. Feel free to create an issue with details on how you see that coming together. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the spirit of minimizing our footprint (mcpadapt is more or less a wrapper around the python sdk client), I think we should probably not create our own parameter here auth_provider
but rather use the existing definition of the sse or streamablehttp client which already take a auth
parameter in the serverparams argument. Then we can just simply pass it through to the client.
Is there any limitations I am not seeing from the approach above?
Maybe we should start in a first PR by introducing the passthrough auth parameter only and test it in a simple way as in: """
Before running, specify running MCP RS server URL.
To spin up RS server locally, see
examples/servers/simple-auth/README.md
cd to the `examples/snippets` directory and run:
uv run oauth-client
"""
import asyncio
from urllib.parse import parse_qs, urlparse
from pydantic import AnyUrl
from mcp import ClientSession
from mcp.client.auth import OAuthClientProvider, TokenStorage
from mcp.client.streamable_http import streamablehttp_client
from mcp.shared.auth import OAuthClientInformationFull, OAuthClientMetadata, OAuthToken
class InMemoryTokenStorage(TokenStorage):
"""Demo In-memory token storage implementation."""
def __init__(self):
self.tokens: OAuthToken | None = None
self.client_info: OAuthClientInformationFull | None = None
async def get_tokens(self) -> OAuthToken | None:
"""Get stored tokens."""
return self.tokens
async def set_tokens(self, tokens: OAuthToken) -> None:
"""Store tokens."""
self.tokens = tokens
async def get_client_info(self) -> OAuthClientInformationFull | None:
"""Get stored client information."""
return self.client_info
async def set_client_info(self, client_info: OAuthClientInformationFull) -> None:
"""Store client information."""
self.client_info = client_info
async def handle_redirect(auth_url: str) -> None:
print(f"Visit: {auth_url}")
async def handle_callback() -> tuple[str, str | None]:
callback_url = input("Paste callback URL: ")
params = parse_qs(urlparse(callback_url).query)
return params["code"][0], params.get("state", [None])[0]
async def main():
"""Run the OAuth client example."""
oauth_auth = OAuthClientProvider(
server_url="http://localhost:8001",
client_metadata=OAuthClientMetadata(
client_name="Example MCP Client",
redirect_uris=[AnyUrl("http://localhost:3000/callback")],
grant_types=["authorization_code", "refresh_token"],
response_types=["code"],
scope="user",
),
storage=InMemoryTokenStorage(),
redirect_handler=handle_redirect,
callback_handler=handle_callback,
)
async with streamablehttp_client("http://localhost:8001/mcp", auth=oauth_auth) as (read, write, _):
async with ClientSession(read, write) as session:
await session.initialize()
tools = await session.list_tools()
print(f"Available tools: {[tool.name for tool in tools.tools]}")
resources = await session.list_resources()
print(f"Available resources: {[r.uri for r in resources.resources]}")
def run():
asyncio.run(main())
if __name__ == "__main__":
run() |
After we can think of carefully adding providers but I am still wondering if it's really something of the responsibility of MCPAdapt to provide and maintain |
This change introduces comprehensive authentication capabilities to MCPAdapt, enabling connections to MCP servers that require authentication. The implementation supports three authentication methods: OAuth 2.0, API Key, and Bearer Token authentication. Closes #44
The OAuth 2.0 implementation includes a complete authorization code flow with support for dynamic client registration. The
LocalBrowserOAuthHandler
provides a browser-based authentication experience, while theBaseOAuthHandler
abstract class allows developers to create custom handlers for specialized environments like headless servers or CLI applications. Token storage is handled through a extendible interface with an includedInMemoryTokenStorage
implementation. Base classes from the official MCP library have been used and re-exported wherever it makes senseThe authentication system integrates transparently with the existing MCPAdapt core through the
auth_provider
parameter in theMCPAdapt
constructor. It supports multiple MCP servers with different authentication methods and works across SSE, and streamable-http.Error handling includes specific exception types for different OAuth failure scenarios, providing clear feedback for timeout, cancellation, network, and configuration errors. The implementation includes comprehensive test coverage and working examples, including an example integration with the Canva MCP server.
Documentation covers all authentication methods with practical examples, custom handler creation guides, and security best practices.
Coverage info: