-
Notifications
You must be signed in to change notification settings - Fork 2.8k
feat: implement SEP-991 URL-based client ID (CIMD) support #1652
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
Changes from 6 commits
7d68025
66c783e
f32cdad
e936893
299fe27
c1e0181
a2630ac
cb3b500
efd73df
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,7 +3,7 @@ | |
| from urllib.parse import urljoin, urlparse | ||
|
|
||
| from httpx import Request, Response | ||
| from pydantic import ValidationError | ||
| from pydantic import AnyUrl, ValidationError | ||
|
|
||
| from mcp.client.auth import OAuthRegistrationError, OAuthTokenError | ||
| from mcp.client.streamable_http import MCP_PROTOCOL_VERSION | ||
|
|
@@ -243,6 +243,75 @@ async def handle_registration_response(response: Response) -> OAuthClientInforma | |
| raise OAuthRegistrationError(f"Invalid registration response: {e}") | ||
|
|
||
|
|
||
| def is_valid_client_metadata_url(url: str | None) -> bool: | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. open to a different break down of helper functions here, this seemed like a reasonably flexible combo.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. seems appropriate to me |
||
| """Validate that a URL is suitable for use as a client_id (CIMD). | ||
|
|
||
| Per SEP-991, the URL must be HTTPS with a non-root pathname. | ||
|
|
||
| Args: | ||
| url: The URL to validate | ||
|
|
||
| Returns: | ||
| True if the URL is a valid HTTPS URL with a non-root pathname | ||
| """ | ||
| if not url: | ||
| return False | ||
| try: | ||
| parsed = urlparse(url) | ||
| return parsed.scheme == "https" and parsed.path not in ("", "/") | ||
| except Exception: # pragma: no cover | ||
|
||
| return False | ||
|
|
||
|
|
||
| def should_use_client_metadata_url( | ||
| oauth_metadata: OAuthMetadata | None, | ||
| client_metadata_url: str | None, | ||
| ) -> bool: | ||
| """Determine if URL-based client ID (CIMD) should be used instead of DCR. | ||
|
|
||
| Per SEP-991, URL-based client IDs should be used when: | ||
| 1. The server advertises client_id_metadata_document_supported=true | ||
| 2. The client has a valid client_metadata_url configured | ||
|
|
||
| Args: | ||
| oauth_metadata: OAuth authorization server metadata | ||
| client_metadata_url: URL-based client ID (already validated) | ||
|
|
||
| Returns: | ||
| True if CIMD should be used, False if DCR should be used | ||
| """ | ||
| if not client_metadata_url: | ||
| return False | ||
|
|
||
| if not oauth_metadata: | ||
| return False | ||
|
|
||
| return oauth_metadata.client_id_metadata_document_supported is True | ||
|
|
||
|
|
||
| def create_client_info_from_metadata_url( | ||
| client_metadata_url: str, redirect_uris: list[AnyUrl] | None = None | ||
| ) -> OAuthClientInformationFull: | ||
| """Create client information using a URL-based client ID (CIMD). | ||
|
|
||
| Per SEP-991, when using URL-based client IDs, the URL itself becomes the client_id | ||
| and no client_secret is used (token_endpoint_auth_method="none"). | ||
|
|
||
| Args: | ||
| client_metadata_url: The URL to use as the client_id | ||
| redirect_uris: The redirect URIs from the client metadata (passed through for | ||
| compatibility with OAuthClientInformationFull which inherits from OAuthClientMetadata) | ||
|
|
||
| Returns: | ||
| OAuthClientInformationFull with the URL as client_id | ||
| """ | ||
| return OAuthClientInformationFull( | ||
| client_id=client_metadata_url, | ||
| token_endpoint_auth_method="none", | ||
| redirect_uris=redirect_uris, | ||
| ) | ||
|
|
||
|
|
||
| async def handle_token_response_scopes( | ||
| response: Response, | ||
| ) -> OAuthToken: | ||
|
|
||
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.
nit: personally think we shouldn't litter comments with references to SEP numbers, hard to keep up-to-date.