o
    i]                    @  s  U d Z ddlmZ ddlZddlZddlZddlZddlZddlZddlm	Z	 ddl
mZmZmZ ddlmZmZ ddlZddlmZ ddlmZ dd	lmZ dd
lmZ ddlmZ ddlmZ ddlmZ ddl m!Z!m"Z" ddl m#Z$ ddl%m&Z& ddl'm(Z( ddl)m*Z*m+Z+m,Z,m-Z-m.Z.m/Z/ ddl0m1Z1 ddl2m3Z3m4Z4 ddl5m6Z6m7Z7 ddl8m9Z9m:Z:m;Z;m<Z<m=Z= ddl>m?Z? ddl@mAZAmBZB ddlCmDZD ddlEmFZF ddlGmHZH ddlImJZJmKZK ddlLmMZM ddlNmOZOmPZP ddlQmRZR dd lSmTZT dd!lUmVZVmWZWmXZXmYZYmZZZm[Z[m\Z\m]Z]m^Z^ er	 eTe_Z`d"Zad#ebd$< d%Zcd#ebd&< d'Zdd#ebd(< G d)d* d*e;ZeG d+d, d,e;ZfG d-d. d.e;ZgG d/d0 d0e;ZhG d1d2 d2e6Zi		3				dOdPdCdDZj			dQdRdIdJZkG dKdL dLe$Z#G dMdN dNeJZldS )Sa  OAuth Proxy Provider for FastMCP.

This provider acts as a transparent proxy to an upstream OAuth Authorization Server,
handling Dynamic Client Registration locally while forwarding all other OAuth flows.
This enables authentication with upstream providers that don't support DCR or have
restricted client registration policies.

Key features:
- Proxies authorization and token endpoints to upstream server
- Implements local Dynamic Client Registration with fixed upstream credentials
- Validates tokens using upstream JWKS
- Maintains minimal local state for bookkeeping
- Enhanced logging with request correlation

This implementation is based on the OAuth 2.1 specification and is designed for
production use with enterprise identity providers.
    )annotationsN)urlsafe_b64encode)TYPE_CHECKINGAnyFinal)	urlencodeurlparse)generate_token)AsyncOAuth2Client)Fernet)PydanticAdapter)AsyncKeyValue)	DiskStore)FernetEncryptionWrapper)TokenErrorResponseTokenSuccessResponse)TokenHandler)PydanticJSONResponse)ClientAuthenticator)AccessTokenAuthorizationCodeAuthorizationParamsAuthorizeErrorRefreshToken
TokenError)cors_middleware)ClientRegistrationOptionsRevocationOptions)OAuthClientInformationFull
OAuthToken)
AnyHttpUrlAnyUrl	BaseModelField	SecretStr)Request)HTMLResponseRedirectResponse)Route)override)settings)OAuthProviderTokenVerifier)AuthorizationHandler)	JWTIssuerderive_jwt_key)validate_redirect_uri)
get_logger)	BUTTON_STYLESDETAIL_BOX_STYLESDETAILS_STYLESINFO_BOX_STYLESREDIRECT_SECTION_STYLESTOOLTIP_STYLEScreate_logocreate_pagecreate_secure_html_responsei  z
Final[int]#DEFAULT_ACCESS_TOKEN_EXPIRY_SECONDSi,   DEFAULT_AUTH_CODE_EXPIRY_SECONDS   HTTP_TIMEOUT_SECONDSc                   @  s   e Zd ZU dZded< ded< ded< ded< ded< ded	< d
ed< ded< dZded< dZded< dZded< dZded< dS )OAuthTransactionzOAuth transaction state for consent flow.

    Stored server-side to track active authorization flows with client context.
    Includes CSRF tokens for consent protection per MCP security best practices.
    strtxn_id	client_idclient_redirect_uriclient_state
str | Nonecode_challengecode_challenge_method	list[str]scopesfloat
created_atNresourceproxy_code_verifier
csrf_tokenfloat | Nonecsrf_expires_at)	__name__
__module____qualname____doc____annotations__rL   rM   rN   rP    rV   rV   e/var/www/html/karishye-ai-python/venv/lib/python3.10/site-packages/fastmcp/server/auth/oauth_proxy.pyr?   m   s   
 r?   c                   @  sZ   e Zd ZU dZded< ded< ded< ded< ded< d	ed
< ded< ded< ded< dS )
ClientCodezClient authorization code with PKCE and upstream tokens.

    Stored server-side after upstream IdP callback. Contains the upstream
    tokens bound to the client's PKCE challenge for secure token exchange.
    r@   coderB   redirect_urirE   rF   rG   rH   rI   dict[str, Any]
idp_tokensrJ   
expires_atrK   NrQ   rR   rS   rT   rU   rV   rV   rV   rW   rX      s   
 rX   c                   @  sl   e Zd ZU dZded< ded< ded< ded< d	ed
< ded< ded< ded< d	ed< eedZded< dS )UpstreamTokenSeta<  Stored upstream OAuth tokens from identity provider.

    These tokens are obtained from the upstream provider (Google, GitHub, etc.)
    and stored in plaintext within this model. Encryption is handled transparently
    at the storage layer via FernetEncryptionWrapper. Tokens are never exposed to MCP clients.
    r@   upstream_token_idaccess_tokenrE   refresh_tokenrO   refresh_token_expires_atrJ   r]   
token_typescoperB   rK   )default_factoryr[   raw_token_dataN)rQ   rR   rS   rT   rU   r#   dictrg   rV   rV   rV   rW   r_      s   
 r_   c                   @  s*   e Zd ZU dZded< ded< ded< dS )
JTIMappingzMaps FastMCP token JTI to upstream token ID.

    This allows stateless JWT validation while still being able to look up
    the corresponding upstream token when tools need to access upstream APIs.
    r@   jtir`   rJ   rK   Nr^   rV   rV   rV   rW   ri      s
   
 ri   c                      sH   e Zd ZU dZeddZded< eddZded< d fddZ  Z	S )ProxyDCRClienta  Client for DCR proxy with configurable redirect URI validation.

    This special client class is critical for the OAuth proxy to work correctly
    with Dynamic Client Registration (DCR). Here's why it exists:

    Problem:
    --------
    When MCP clients use OAuth, they dynamically register with random localhost
    ports (e.g., http://localhost:55454/callback). The OAuth proxy needs to:
    1. Accept these dynamic redirect URIs from clients based on configured patterns
    2. Use its own fixed redirect URI with the upstream provider (Google, GitHub, etc.)
    3. Forward the authorization code back to the client's dynamic URI

    Solution:
    ---------
    This class validates redirect URIs against configurable patterns,
    while the proxy internally uses its own fixed redirect URI with the upstream
    provider. This allows the flow to work even when clients reconnect with
    different ports or when tokens are cached.

    Without proper validation, clients could get "Redirect URI not registered" errors
    when trying to authenticate with cached tokens, or security vulnerabilities could
    arise from accepting arbitrary redirect URIs.
    N)defaultlist[str] | Noneallowed_redirect_uri_patternsrE   client_namerZ   AnyUrl | Nonereturnr!   c                   s2   |durt || jdr|S t  |S t  |S )a;  Validate redirect URI against allowed patterns.

        Since we're acting as a proxy and clients register dynamically,
        we validate their redirect URIs against configurable patterns.
        This is essential for cached token scenarios where the client may
        reconnect with a different port.
        N)rZ   allowed_patterns)r0   rn   super)selfrZ   	__class__rV   rW   r0      s   z$ProxyDCRClient.validate_redirect_uri)rZ   rp   rq   r!   )
rQ   rR   rS   rT   r#   rn   rU   ro   r0   __classcell__rV   rV   ru   rW   rk      s
   
 rk   Application Access RequestrB   r@   rZ   rI   rH   rA   rN   ro   rE   titleserver_nameserver_icon_urlserver_website_urlclient_website_urlrq   c                   s  ddl   |p	| } |pd}|	r# |	}d| d| d}n|}d| d| d	} |}d
| d}d |p?| fd |
pGdfd| fd|fd|r^d fdd|D ndfg}ddd |D }d| d}d| d| d}d}dt||pdd  d!| d"| d"| d"| d#| d$}tt t t t t	 }t
|}|j }d%d&g}|r|d'vr|| d( d)|}d*| }t||||d+S ),zCCreate a styled HTML consent page for OAuth authorization requests.r   NFastMCPz	<a href="zE" target="_blank" rel="noopener noreferrer" class="server-name-link">z</a>zG
        <div class="info-box">
            <p>The application <strong>z1</strong> wants to access the MCP server <strong>zZ</strong>. Please ensure you recognize the callback address below.</p>
        </div>
    z
        <div class="redirect-section">
            <span class="label">Credentials will be sent to:</span>
            <div class="value">z</div>
        </div>
    zApplication NamezApplication WebsiteN/AzApplication IDzRedirect URIzRequested Scopesz, c                 3  s    | ]}  |V  qd S Nescape).0shtml_modulerV   rW   	<genexpr>  s    z&create_consent_html.<locals>.<genexpr>None
c                 S  s"   g | ]\}}d | d| dqS )zH
        <div class="detail-row">
            <div class="detail-label">z.:</div>
            <div class="detail-value">z</div>
        </div>
        rV   r   labelvaluerV   rV   rW   
<listcomp>$  s    z'create_consent_html.<locals>.<listcomp>zx
        <details>
            <summary>Advanced Details</summary>
            <div class="detail-box">
                z+
            </div>
        </details>
    zo
        <form id="consentForm" method="POST" action="">
            <input type="hidden" name="txn_id" value="z?" />
            <input type="hidden" name="csrf_token" value="a`  " />
            <input type="hidden" name="submit" value="true" />
            <div class="button-group">
                <button type="submit" name="action" value="approve" class="btn-approve">Allow Access</button>
                <button type="submit" name="action" value="deny" class="btn-deny">Deny</button>
            </div>
        </form>
    u  
        <div class="help-link-container">
            <span class="help-link">
                Why am I seeing this?
                <span class="tooltip">
                    This FastMCP server requires your consent to allow a new client
                    to connect. This protects you from <a
                    href="https://modelcontextprotocol.io/specification/2025-06-18/basic/security_best_practices#confused-deputy-problem"
                    target="_blank" class="tooltip-link">confused deputy
                    attacks</a>, where malicious clients could impersonate you
                    and steal access.<br><br>
                    <a
                    href="https://gofastmcp.com/servers/auth/oauth-proxy#confused-deputy-attacks"
                    target="_blank" class="tooltip-link">Learn more about
                    FastMCP security →</a>
                </span>
            </span>
        </div>
    -
        <div class="container">
            icon_urlalt_textz=
            <h1>Application Access Request</h1>
            
            z
        </div>
        z
    zhttps:zhttp:)httphttps: zbdefault-src 'none'; style-src 'unsafe-inline'; img-src https: data:; base-uri 'none'; form-action contentry   additional_styles
csp_policy)htmlr   joinr8   r5   r6   r4   r3   r2   r7   r   schemelowerappendr9   )rB   rZ   rI   rA   rN   ro   ry   rz   r{   r|   r}   client_displayserver_name_escapedwebsite_url_escapedserver_display	intro_boxredirect_uri_escapedredirect_sectiondetail_rowsdetail_rows_htmladvanced_detailsform	help_linkr   r   parsed_redirectredirect_schemeform_action_schemesform_action_directiver   rV   r   rW   create_consent_html   s   

		


r   error_titleerror_messageerror_detailsdict[str, str] | Nonec              	     s   ddl   |}d| d}d}|r'd fdd| D }d	| d
}dt||p-dd d |  d| d| d	}	tt t d }
d}t|	| |
|dS )a  Create a styled HTML error page for OAuth errors.

    Args:
        error_title: The error title (e.g., "OAuth Error", "Authorization Failed")
        error_message: The main error message to display
        error_details: Optional dictionary of error details to show (e.g., {"Error Code": "invalid_client"})
        server_name: Optional server name to display
        server_icon_url: Optional URL to server icon/logo

    Returns:
        Complete HTML page as a string
    r   Nz5
        <div class="info-box error">
            <p>z</p>
        </div>
     r   c                   s.   g | ]\}}d   | d  | dqS )zP
            <div class="detail-row">
                <div class="detail-label">z2:</div>
                <div class="detail-value">z&</div>
            </div>
            r   r   r   rV   rW   r     s    z%create_error_html.<locals>.<listcomp>z
            <details>
                <summary>Error Details</summary>
                <div class="detail-box">
                    z7
                </div>
            </details>
        r   r~   r   z
            <h1>z</h1>
            r   z
        </div>
    zI
        .info-box.error {
            color: #111827;
        }
        zTdefault-src 'none'; style-src 'unsafe-inline'; img-src https: data:; base-uri 'none'r   )	r   r   r   itemsr8   r5   r4   r3   r9   )r   r   r   rz   r{   error_message_escaped	error_boxdetails_sectionr   r   r   r   rV   r   rW   create_error_html  sN   


r   c                      s"   e Zd ZdZd fddZ  ZS )r   a9  TokenHandler that returns OAuth 2.1 compliant error responses.

    The MCP SDK always returns HTTP 400 for all client authentication issues.
    However, OAuth 2.1 Section 5.3 and the MCP specification require that
    invalid or expired tokens MUST receive a HTTP 401 response.

    This handler extends the base MCP SDK TokenHandler to transform client
    authentication failures into OAuth 2.1 compliant responses:
    - Changes 'unauthorized_client' to 'invalid_client' error code
    - Returns HTTP 401 status code instead of 400 for client auth failures

    Per OAuth 2.1 Section 5.3: "The authorization server MAY return an HTTP 401
    (Unauthorized) status code to indicate which HTTP authentication schemes
    are supported."

    Per MCP spec: "Invalid or expired tokens MUST receive a HTTP 401 response."
    obj)TokenSuccessResponse | TokenErrorResponsec                   sR   t |tr#|jdkr#|jr#d|jv r#ttd|j|jdddddd	S t |S )
zGOverride response method to provide OAuth 2.1 compliant error handling.unauthorized_clientzInvalid client_idinvalid_client)errorerror_description	error_urii  zno-storezno-cache)zCache-ControlPragma)r   status_codeheaders)
isinstancer   r   r   r   r   rs   response)rt   r   ru   rV   rW   r     s$   

zTokenHandler.response)r   r   )rQ   rR   rS   rT   r   rw   rV   rV   ru   rW   r     s    r   c                      sL  e Zd ZdZddddddddddddddd~ fd"d#Zdd&d'Zedd*d+Zedd/d0Zedd4d5Z	edd8d9Z
edd<d=Zdd@dAZddEdFZddIdJZddLdMZ	dd fdPdQZddUdVZddXdYZdd\d]Zdd_d`ZddbdcZddedfZddgdhZddjdkZddpdqZddudvZddwdxZddydzZdd|d}Z  ZS )
OAuthProxyak  OAuth provider that presents a DCR-compliant interface while proxying to non-DCR IDPs.

    Purpose
    -------
    MCP clients expect OAuth providers to support Dynamic Client Registration (DCR),
    where clients can register themselves dynamically and receive unique credentials.
    Most enterprise IDPs (Google, GitHub, Azure AD, etc.) don't support DCR and require
    pre-registered OAuth applications with fixed credentials.

    This proxy bridges that gap by:
    - Presenting a full DCR-compliant OAuth interface to MCP clients
    - Translating DCR registration requests to use pre-configured upstream credentials
    - Proxying all OAuth flows to the upstream IDP with appropriate translations
    - Managing the state and security requirements of both protocols

    Architecture Overview
    --------------------
    The proxy maintains a single OAuth app registration with the upstream provider
    while allowing unlimited MCP clients to register and authenticate dynamically.
    It implements the complete OAuth 2.1 + DCR specification for clients while
    translating to whatever OAuth variant the upstream provider requires.

    Key Translation Challenges Solved
    ---------------------------------
    1. Dynamic Client Registration:
       - MCP clients expect to register dynamically and get unique credentials
       - Upstream IDPs require pre-registered apps with fixed credentials
       - Solution: Accept DCR requests, return shared upstream credentials

    2. Dynamic Redirect URIs:
       - MCP clients use random localhost ports that change between sessions
       - Upstream IDPs require fixed, pre-registered redirect URIs
       - Solution: Use proxy's fixed callback URL with upstream, forward to client's dynamic URI

    3. Authorization Code Mapping:
       - Upstream returns codes for the proxy's redirect URI
       - Clients expect codes for their own redirect URIs
       - Solution: Exchange upstream code server-side, issue new code to client

    4. State Parameter Collision:
       - Both client and proxy need to maintain state through the flow
       - Only one state parameter available in OAuth
       - Solution: Use transaction ID as state with upstream, preserve client's state

    5. Token Management:
       - Clients may expect different token formats/claims than upstream provides
       - Need to track tokens for revocation and refresh
       - Solution: Store token relationships, forward upstream tokens transparently

    OAuth Flow Implementation
    ------------------------
    1. Client Registration (DCR):
       - Accept any client registration request
       - Store ProxyDCRClient that accepts dynamic redirect URIs

    2. Authorization:
       - Store transaction mapping client details to proxy flow
       - Redirect to upstream with proxy's fixed redirect URI
       - Use transaction ID as state parameter with upstream

    3. Upstream Callback:
       - Exchange upstream authorization code for tokens (server-side)
       - Generate new authorization code bound to client's PKCE challenge
       - Redirect to client's original dynamic redirect URI

    4. Token Exchange:
       - Validate client's code and PKCE verifier
       - Return previously obtained upstream tokens
       - Clean up one-time use authorization code

    5. Token Refresh:
       - Forward refresh requests to upstream using authlib
       - Handle token rotation if upstream issues new refresh token
       - Update local token mappings

    State Management
    ---------------
    The proxy maintains minimal but crucial state:
    - _oauth_transactions: Active authorization flows with client context
    - _client_codes: Authorization codes with PKCE challenges and upstream tokens
    - _access_tokens, _refresh_tokens: Token storage for revocation
    - Token relationship mappings for cleanup and rotation

    Security Considerations
    ----------------------
    - PKCE enforced end-to-end (client to proxy, proxy to upstream)
    - Authorization codes are single-use with short expiry
    - Transaction IDs are cryptographically random
    - All state is cleaned up after use to prevent replay
    - Token validation delegates to upstream provider

    Provider Compatibility
    ---------------------
    Works with any OAuth 2.0 provider that supports:
    - Authorization code flow
    - Fixed redirect URI (configured in provider's app settings)
    - Standard token endpoint

    Handles provider-specific requirements:
    - Google: Ensures minimum scope requirements
    - GitHub: Compatible with OAuth Apps and GitHub Apps
    - Azure AD: Handles tenant-specific endpoints
    - Generic: Works with any spec-compliant provider
    NT)upstream_revocation_endpointredirect_path
issuer_urlservice_documentation_urlallowed_client_redirect_urisvalid_scopesforward_pkcetoken_endpoint_auth_methodextra_authorize_paramsextra_token_paramsclient_storagejwt_signing_keyrequire_authorization_consentupstream_authorization_endpointr@   upstream_token_endpointupstream_client_idupstream_client_secretr   rE   token_verifierr,   base_urlAnyHttpUrl | strr   r   AnyHttpUrl | str | Noner   r   rm   r   r   boolr   r   r   r   r   AsyncKeyValue | Noner   str | bytes | Noner   c                  sZ  t d|p|jd}|rtddnd}t j||	|
|||jd || _|| _|| _t|d| _	|| _
d| jp7g | _|s@d| _n|d	rG|nd	| | _t|trY|sYtd
 || _|| _|| _|| _|sltd |poi | _|pti | _|du rt|dd}t|trt|dk rtd t|dd}tt| jt| jd	 d|d| _|du rt|  dd}t!t"t#j$d dt%|dd}|| _&t| jd| _'| j'std t(t) | j&t)ddd| _*t(t+ | j&t+ddd| _,t(t- | j&t-ddd| _.t(t/ | j&t/ddd| _0t(t1 | j&t1ddd| _2i | _3i | _4i | _5i | _6|| _7t8d | j dS )!a  Initialize the OAuth proxy provider.

        Args:
            upstream_authorization_endpoint: URL of upstream authorization endpoint
            upstream_token_endpoint: URL of upstream token endpoint
            upstream_client_id: Client ID registered with upstream server
            upstream_client_secret: Client secret for upstream server
            upstream_revocation_endpoint: Optional upstream revocation endpoint
            token_verifier: Token verifier for validating access tokens
            base_url: Public URL of the server that exposes this FastMCP server; redirect path is
                relative to this URL
            redirect_path: Redirect path configured in upstream OAuth app (defaults to "/auth/callback")
            issuer_url: Issuer URL for OAuth metadata (defaults to base_url)
            service_documentation_url: Optional service documentation URL
            allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
                Patterns support wildcards (e.g., "http://localhost:*", "https://*.example.com/*").
                If None (default), only localhost redirect URIs are allowed.
                If empty list, all redirect URIs are allowed (not recommended for production).
                These are for MCP clients performing loopback redirects, NOT for the upstream OAuth app.
            valid_scopes: List of all the possible valid scopes for a client.
                These are advertised to clients through the `/.well-known` endpoints. Defaults to `required_scopes` if not provided.
            forward_pkce: Whether to forward PKCE to upstream server (default True).
                Enable for providers that support/require PKCE (Google, Azure, AWS, etc.).
                Disable only if upstream provider doesn't support PKCE.
            token_endpoint_auth_method: Token endpoint authentication method for upstream server.
                Common values: "client_secret_basic", "client_secret_post", "none".
                If None, authlib will use its default (typically "client_secret_basic").
            extra_authorize_params: Additional parameters to forward to the upstream authorization endpoint.
                Useful for provider-specific parameters like Auth0's "audience".
                Example: {"audience": "https://api.example.com"}
            extra_token_params: Additional parameters to forward to the upstream token endpoint.
                Useful for provider-specific parameters during token exchange.
            client_storage: Storage backend for OAuth state (client registrations, tokens).
                If None, an encrypted DiskStore will be created in the data directory.
            jwt_signing_key: Secret for signing FastMCP JWT tokens (any string or bytes).
                If bytes are provided, they will be used as-is.
                If a string is provided, it will be derived into a 32-byte key using PBKDF2 (1.2M iterations).
                If not provided, it will be derived from the upstream client secret using HKDF.
            require_authorization_consent: Whether to require user consent before authorizing clients (default True).
                When True, users see a consent screen before being redirected to the upstream IdP.
                When False, authorization proceeds directly without user confirmation.
                SECURITY WARNING: Only disable for local development or testing environments.
        T)enabledr   )r   N)r   r   r   client_registration_optionsrevocation_optionsrequired_scopes)secret_valuer   z/auth/callback/zqallowed_client_redirect_uris is empty list; no redirect URIs will be accepted. This will block all OAuth clients.zAuthorization consent screen disabled - only use for local development or testing. In production, this screen protects against confused deputy attacks.zfastmcp-jwt-signing-key)high_entropy_materialsalt   zmjwt_signing_key is less than 12 characters; it is recommended to use a longer. string for the key derivation.)low_entropy_materialr   z/mcp)issueraudiencesigning_keyzfastmcp-storage-encryption-keyzoauth-proxy)	directorykey)	key_valuefernetzhttps://zKUsing non-secure cookies for development; deploy with HTTPS for production.zmcp-upstream-tokens)r   pydantic_modeldefault_collectionraise_on_validation_errorzmcp-oauth-proxy-clientszmcp-oauth-transactionszmcp-authorization-codeszmcp-jti-mappingsz8Initialized OAuth proxy provider with upstream server %s)9r   r   r   rs   __init__ _upstream_authorization_endpoint_upstream_token_endpoint_upstream_client_idr$   _upstream_client_secret_upstream_revocation_endpointr   _default_scope_str_redirect_path
startswithr   listloggerwarning_allowed_client_redirect_uris_forward_pkce_token_endpoint_auth_method_require_authorization_consent_extra_authorize_params_extra_token_paramsr/   r@   lenr.   r   rstrip_jwt_issuerdecoder   r   r*   homer   _client_storage	_is_httpsr   r_   _upstream_token_storerk   _client_storer?   _transaction_storerX   _code_storeri   _jti_mapping_store_access_tokens_refresh_tokens_access_to_refresh_refresh_to_access_token_validatordebug)rt   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   storage_encryption_keyru   rV   rW   r     s   O



		
zOAuthProxy.__init__rq   tuple[str, str]c                 C  s4   t d}t|  }t| d}||fS )zGenerate PKCE code verifier and challenge pair.

        Returns:
            Tuple of (code_verifier, code_challenge) using S256 method
        0   =)r	   hashlibsha256encodedigestr   r
  r  )rt   code_verifierchallenge_bytesrF   rV   rV   rW   _generate_pkce_pair  s   zOAuthProxy._generate_pkce_pairrB   !OAuthClientInformationFull | Nonec                   s4   | j j|dI dH  }sdS |jdu r| j|_|S )zGet client information by ID. This is generally the random ID
        provided to the DCR client during registration, not the upstream client ID.

        For unregistered clients, returns None (which will raise an error in the SDK).
        r   N)r  getrn   r  )rt   rB   clientrV   rV   rW   
get_client  s   
zOAuthProxy.get_clientclient_infor   r   c                   s   |j du r
tdt|j |j|jptdg|jpddg|jp!| jd| j	t
|ddd}| jj|j |d	I dH  |jrG|jD ]}td
| q>td|j |jrWt|j dS d dS )aM  Register a client locally

        When a client registers, we create a ProxyDCRClient that is more
        forgiving about validating redirect URIs, since the DCR client's
        redirect URI will likely be localhost or unknown to the proxied IDP. The
        proxied IDP only knows about this server's fixed redirect URI.
        Nz-client_id is required for client registrationzhttp://localhostauthorization_coderb   nonero   )rB   client_secretredirect_urisgrant_typesre   r   rn   ro   )r   r   zClient registered with redirect_uri: %s - if restricting redirect URIs, ensure this pattern is allowed in allowed_client_redirect_urisz*Registered client %s with %d redirect URIsr   )rB   
ValueErrorrk   r+  r,  r!   r-  re   r   r  getattrr  putr   r  r  )rt   r(  proxy_clienturirV   rV   rW   register_client  s>   



zOAuthProxy.register_clientr&  paramsr   c           	        s$  t d}d}d}| jr|jr|  \}}td| |jdu r'tdddt	||jt
|j|jp3d|jt|dd	|jp>g t t|d
d|d
}| jj||ddI dH  | jss| || }td||j|rnd |S d |S t
| jd d| }td||j|rd |S d |S )a  Start OAuth transaction and route through consent interstitial.

        Flow:
        1. Store transaction with client details and PKCE (if forwarding)
        2. Return local /consent URL; browser visits consent first
        3. Consent handler redirects to upstream IdP if approved/already approved

        If consent is disabled (require_authorization_consent=False), skip the consent screen
        and redirect directly to the upstream IdP.
            NzLGenerated proxy PKCE for transaction %s (forwarding client PKCE to upstream)r   Client ID is requiredr   r   r   rG   S256rL   )
rA   rB   rC   rD   rF   rG   rI   rK   rL   rM     r   r   ttlzyStarting OAuth transaction %s for client %s, redirecting directly to upstream IdP (consent disabled, PKCE forwarding: %s)r   disabledr   z/consent?txn_id=z^Starting OAuth transaction %s for client %s, redirecting to consent page (PKCE forwarding: %s))secretstoken_urlsafer  rF   r#  r   r  rB   r   r?   r@   rZ   stater/  rI   timer  r0  r  _build_upstream_authorize_url
model_dumpr   r  )	rt   r&  r4  rA   rM   proxy_code_challengetransactionupstream_urlconsent_urlrV   rV   rW   	authorize  sp   



zOAuthProxy.authorizer)  AuthorizationCode | Nonec              	     s   | j j|dI dH }|std| dS t |jkr.td| | j j|dI dH }dS |j|jkr?td|j|j dS |jdu rJtdddt	||jt
|jd	d
|j|j|jp\ddS )zLoad authorization code for validation.

        Look up our client code and return authorization code object
        with PKCE challenge for validation.
        r   Nz Authorization code not found: %szAuthorization code expired: %sz/Authorization code client ID mismatch: %s vs %sr   r6  r7  )urlTr   )rY   rB   rZ    redirect_uri_provided_explicitlyrI   r]   rF   )r  r%  r   r  r@  r]   deleterB   r   r   r!   rZ   rI   rF   )rt   r&  r)  
code_model_rV   rV   rW   load_authorization_code$  s:   

z"OAuthProxy.load_authorization_coder   r   c                   s  | j j|jdI dH }|std|j tdd|j}| j j|jdI dH  t	d}t	d}|dr<t	dnd}t
|dt}d}	d}
|drsd	|v rft
|d	 }	t |	 }
td
|	 nd}	t |	 }
td t||d |dr|d nd|
t | |ddd|j|jpdt |d
}| jj|||	p|dI dH  td|dd  |jdu rtdd| jj|j|j||d}d}|r|	r| jj|j|j||	d}| jj|t||t d|dI dH  |r| jj|t||t dddI dH  t||j|jt
t | d| j|< |r3t||j|jdd| j|< || j|< || j|< td|j|dd |rF|dd nd t|d||d|jdS )a  Exchange authorization code for FastMCP-issued tokens.

        Implements the token factory pattern:
        1. Retrieves upstream tokens from stored authorization code
        2. Extracts user identity from upstream token
        3. Encrypts and stores upstream tokens
        4. Issues FastMCP-signed JWT tokens
        5. Returns FastMCP tokens (NOT upstream tokens)

        PKCE validation is handled by the MCP framework before this method is called.
        r   Nz0Authorization code not found in client codes: %sinvalid_grantzAuthorization code not foundr5  rb   
expires_inrefresh_expires_in,Upstream refresh token expires in %d seconds ' z;Upstream refresh token expiry unknown, using 30-day defaultra   rd   Bearerr   r   )
r`   ra   rb   rc   r]   rd   re   rB   rK   rg   r:  z)Stored encrypted upstream tokens (jti=%s)   r   r6  rB   rI   rj   rP  rj   r`   rK   tokenrB   rI   r]   zCIssued FastMCP tokens for client=%s (access_jti=%s, refresh_jti=%s)r*  ra   rd   rP  rb   re   ) r  r%  rY   r   r   r   r\   rK  r=  r>  intr;   r@  r  r_   r   rI   rB   r  r0  r	  issue_access_tokenissue_refresh_tokenr  ri   r   r  r   r  r  r  r   )rt   r&  r)  rL  r\   r`   
access_jtirefresh_jtirP  rQ  rc   upstream_token_setfastmcp_access_tokenfastmcp_refresh_tokenrV   rV   rW   exchange_authorization_codeS  s   










		



z&OAuthProxy.exchange_authorization_coderb   RefreshToken | Nonec                   s   | j |S )z&Load refresh token from local storage.)r  r%  )rt   r&  rb   rV   rV   rW   load_refresh_token  s   zOAuthProxy.load_refresh_tokenr   rI   rH   c              
     s  z| j |j}|d }W n ty& } ztd| tdd|d}~ww | jj|dI dH }|sBt	d|dd  tdd	| j
j|jdI dH }|s_t	d
|jdd  tdd|jslt	d tddt| j| j | jtd}	z*td|dd  |	jd'| j|j|rd|ndd| jI dH }
td W n ty } zt	d| tdd| |d}~ww t|
dt}|
d |_t | |_d}|
d }r||jkr||_td d|
v rt|
d }t | |_td| n|jrt|jt  }n	d}t | |_|
|_| j
j|j||p4|jr3t|jt  nddI dH  |jdu rEtddt d}| j j!|j|||d }| jj|t"||jt d!|dI dH  t d}| j j#|j|||pydd }|pd}| jj|t"||jt d!|dI dH  | jj$|dI dH  td" t%||j|tt | d#| j&|< t'||j|dd#| j(|< || j)|< || j*|< | j(+|jd | j*+|jd}|r| j&+|d | j)+|d t,d$|j|dd |dd  t-|d%||d|d&S )(a  Exchange FastMCP refresh token for new FastMCP access token.

        Implements two-tier refresh:
        1. Verify FastMCP refresh token
        2. Look up upstream token via JTI mapping
        3. Refresh upstream token with upstream provider
        4. Update stored upstream token
        5. Issue new FastMCP access token
        6. Keep same FastMCP refresh token (unless upstream rotates)
        rj   z+FastMCP refresh token validation failed: %srO  zInvalid refresh tokenNr   z+JTI mapping not found for refresh token: %srU  zRefresh token mapping not foundz Upstream token set not found: %szUpstream token not foundz#No upstream refresh token availablez$Refresh not supported for this tokenrB   r+  r   timeoutz"Refreshing upstream token (jti=%s)r   )rI  rb   re   z%Successfully refreshed upstream tokenz!Upstream token refresh failed: %szUpstream refresh failed: rP  ra   rb   zUpstream refresh token rotatedrQ  rR  rS  r:  r   r6  r5  rV  rW  zCRotated refresh token (old JTI invalidated - one-time use enforced)rX  zYIssued new FastMCP tokens (rotated refresh) for client=%s (access_jti=%s, refresh_jti=%s)rT  rZ  rV   ).r	  verify_tokenrY  	Exceptionr   r  r   r  r%  r   r  r`   rb   r
   r   r   get_secret_valuer  r>   r   r   r  r[  r;   ra   r@  r]   rc   rg   r0  rB   r=  r>  r\  ri   r]  rK  r   r  r   r  r  r  popinfor   )rt   r&  rb   rI   refresh_payloadr_  ejti_mappingr`  oauth_clienttoken_responsenew_expires_innew_refresh_expires_innew_upstream_refreshnew_access_jtinew_fastmcp_accessnew_refresh_jtinew_fastmcp_refreshrefresh_ttl
old_accessrV   rV   rW   exchange_refresh_token  s.  














	



z!OAuthProxy.exchange_refresh_tokenrY  AccessToken | Nonec              
     s   zW| j |}|d }| jj|dI dH }|s!td| W dS | jj|jdI dH }|s8td|j W dS | j|j	I dH }|sLtd W dS td|dd  |W S  t
yq } ztd	| W Y d}~dS d}~ww )
a  Validate FastMCP JWT by swapping for upstream token.

        This implements the token swap pattern:
        1. Verify FastMCP JWT signature (proves it's our token)
        2. Look up upstream token via JTI mapping
        3. Decrypt upstream token
        4. Validate upstream token with provider (GitHub API, JWT validation, etc.)
        5. Return upstream validation result

        The FastMCP JWT is a reference token - all authorization data comes
        from validating the upstream token via the TokenVerifier.
        rj   r   NzJTI mapping not found: %szUpstream token not found: %sz Upstream token validation failedz5Token swap successful for JTI=%s (upstream validated)rU  z Token swap validation failed: %s)r	  rh  r  r%  r   r  r  r`   r  ra   ri  )rt   rY  payloadrj   ro  r`  	validatedrn  rV   rV   rW   load_access_token  s>   

zOAuthProxy.load_access_tokenAccessToken | RefreshTokenc              
     sV  t |tr'| j|jd | j|jd}|r&| j|d | j|d n | j|jd | j|jd}|rG| j|d | j|d | jrz<t	j
td4 I dH %}|j| jd|ji| j| j fdI dH  td W d  I dH  n1 I dH sw   Y  W n ty } ztd| W Y d}~n
d}~ww td td dS )	zRevoke token locally and with upstream server if supported.

        Removes tokens from local storage and attempts to revoke them with
        the upstream server if a revocation endpoint is configured.
        N)rg  rY  )dataauthz/Successfully revoked token with upstream serverz/Failed to revoke token with upstream server: %sz*No upstream revocation endpoint configuredzToken revoked successfully)r   r   r  rk  rY  r  r  r  r   httpxAsyncClientr>   postr   r   rj  r   r  ri  r   )rt   rY  paired_refreshpaired_accesshttp_clientrn  rV   rV   rW   revoke_token  sF   
(
zOAuthProxy.revoke_tokenmcp_pathlist[Route]c           
        s  t  |}g }d}d}tdt| d t|D ]\}}td| d| dt|dd d	t|d
d  t|trj|j	dkrj|j
durjd|j
v sQd|j
v rjd}t| | jddd}|td|jddgd qt|tr|j	dkr|j
durd|j
v rd}t| t| d}	|tdt|	jddgddgd q|| q|t| j| jdgd |td| jddgd td| d| dt| d |S )a  Get OAuth routes with custom handlers for better error UX.

        This method creates standard OAuth routes and replaces:
        - /authorize endpoint: Enhanced error responses for unregistered clients
        - /token endpoint: OAuth 2.1 compliant error codes

        Args:
            mcp_path: The path where the MCP endpoint is mounted (e.g., "/mcp")
                This is used to advertise the resource URL in metadata.
        Fz0get_routes called - configuring OAuth routes in z routeszRoute z: z	 - path: pathr   z, methods: methodsz
/authorizeNGETPOSTT)providerr   rz   r{   )r  endpointr  z/token)r  client_authenticatorOPTIONSz/consentu0   ✅ OAuth routes configured: authorize_endpoint=z, token_endpoint=z, total routes=z$ (includes OAuth callback + consent))rs   
get_routesr   r  r  	enumerater/  r   r(   r  r  r-   r   r   handler   r   r   r   _handle_idp_callback_handle_consent)
rt   r  routescustom_routestoken_route_foundauthorize_route_foundirouteauthorize_handlertoken_handlerru   rV   rW   r  9  s   *

	




zOAuthProxy.get_routesrequestr%   HTMLResponse | RedirectResponsec                   s4  zp|j d}|j d}|j d}|r=|j d}td|| tdd|p*d |r2d	|ind
d}t|ddW S |rA|sStd tddd}t|ddW S | jj|dI d
H }|srtd| tddd}t|ddW S | }t| j	| j
 | jtd}	z]t| jd | j }
td|
  | j||
d}|d}|r||d< td| | jr|| j td|t| j  |	jd5i |I d
H }td| dt| d W n) ty	 } ztd| tdd | d}t|d!dW  Y d
}~W S d
}~ww td"}tt t }| j j!|t"||d# |d$ |d% |d& |d' ||t d(	td)I d
H  | jj#|dI d
H  |d$ }|d* }||d+}d,|v rXd-nd,}| | t$| }td.|  t%|d/d0W S  ty } ztjd1|d2d3 tdd4d}t|d!dW  Y d
}~S d
}~ww )6a  Handle callback from upstream IdP and forward to client.

        This implements the DCR-compliant callback forwarding:
        1. Receive IdP callback with code and txn_id as state
        2. Exchange IdP code for tokens (server-side)
        3. Generate our own client code bound to PKCE challenge
        4. Redirect to client's callback with client code and original state
        rY   r?  r   r   zIdP callback error: %s - %szOAuth ErrorzAuthentication failed: zUnknown errorz
Error CodeN)r   r   r     )r   r   z+IdP callback missing code or transaction IDzHMissing authorization code or transaction ID from the identity provider.)r   r   r   z,IdP callback with invalid transaction ID: %szNInvalid or expired authorization transaction. Please try authenticating again.rf  r   z2Exchanging IdP code for tokens with redirect_uri: )rI  rY   rZ   rM   r!  zBIncluding proxy code_verifier in token exchange for transaction %sz4Adding extra token parameters for transaction %s: %sz9Successfully exchanged IdP code for tokens (transaction: z, PKCE: )zIdP token exchange failed: %sz.Token exchange with identity provider failed: i  r5  rB   rC   rF   rG   rI   )	rY   rB   rZ   rF   rG   rI   r\   r]   rK   r:  rD   )rY   r?  ?&z.Forwarding to client callback for transaction .  rI  r   z!Error in IdP callback handler: %sT)exc_infozIInternal server error during OAuth callback processing. Please try again.rV   )&query_paramsr%  r   r   r   r&   r  rB  r
   r   r   rj  r  r>   r@   r   r  r   r  r   r  updater   keysfetch_tokenr   ri  r=  r>  r[  r@  r<   r  r0  rX   rK  r   r'   )rt   r  idp_coderA   r   r   html_contenttransaction_modelrD  rp  idp_redirect_uritoken_paramsrM   r\   rn  client_codecode_expires_atrC   rD   callback_params	separatorclient_callback_urlrV   rV   rW   r    s   


	zOAuthProxy._handle_idp_callbackr2  c                 C  sV   t |}|jpd}|j  d|j  | }|dr)t|dkr)|dd }|S )z9Normalize a URI to a canonical form for consent tracking.r   z://r      N)r   r  r   r   netlocendswithr  )rt   r2  parsedr  
normalizedrV   rV   rW   _normalize_uriB  s   
zOAuthProxy._normalize_urirZ   str | AnyUrlc                 C  s   |  t|}| d| S )zICreate a stable key for consent tracking from client_id and redirect_uri.r   )r  r@   )rt   rB   rZ   r  rV   rV   rW   _make_client_keyK  s   zOAuthProxy._make_client_key	base_namec                 C  s   | j rd| S d| S )zCReturn secure cookie name for HTTPS, fallback for HTTP development.z__Host-__)r  )rt   r  rV   rV   rW   _cookie_nameP  s   

zOAuthProxy._cookie_namer}  c                 C  sB   | j   }t|| tj }t	|
 }| d| S )zdSign a cookie payload with HMAC-SHA256.

        Returns: base64(payload).base64(signature)
        .)r   rj  r  hmacnewr  r  r   base64	b64encoder
  )rt   r}  r   	signaturesignature_b64rV   rV   rW   _sign_cookieV  s   zOAuthProxy._sign_cookiesigned_valuec                 C  s   z5d|vrW dS | dd\}}| j  }t|| tj }t	
| }t||s3W dS |W S  ty?   Y dS w )ztVerify and extract payload from signed cookie.

        Returns: payload if signature valid, None otherwise
        r  Nr  )rsplitr   rj  r  r  r  r  r  r   r  	b64decodecompare_digestri  )rt   r  r}  r  r   expected_sigprovided_sigrV   rV   rW   _verify_cookiea  s   zOAuthProxy._verify_cookiec                 C  s   |  |}|j|p|jd| }|sg S z.| |}|s)td| g W S t| }t	
| }t|trDdd |D W S W g S  tyV   td| Y g S w )z_Decode and verify a signed base64-encoded JSON list from cookie. Returns [] if missing/invalid.r  z+Cookie signature verification failed for %sc                 S  s   g | ]}t |qS rV   )r@   )r   xrV   rV   rW   r     s    z2OAuthProxy._decode_list_cookie.<locals>.<listcomp>z-Failed to decode cookie %s; treating as empty)r  cookiesr%  r  r   r  r  r  r  jsonloadsr
  r   r   ri  )rt   r  r  secure_namerawr}  r  r   rV   rV   rW   _decode_list_cookiex  s&   


zOAuthProxy._decode_list_cookievaluesc                 C  s*   t j|dd }t| }| |S )znEncode values to base64 and sign with HMAC.

        Returns: signed cookie value (payload.signature)
        ),r   )
separators)r  dumpsr  r  r  r
  r  )rt   r  r}  payload_b64rV   rV   rW   _encode_list_cookie  s   
zOAuthProxy._encode_list_cookier   	value_b64max_ager[  c              	   C  s(   |  |}|j|||| jdddd d S )NTlaxr   )r  securehttponlysamesiter  )r  
set_cookier  )rt   r   r  r  r  namerV   rV   rW   _set_list_cookie  s   

zOAuthProxy._set_list_cookierA   rD  r[   c           
      C  s   d| j t| jd | j |d}|dp| jpg }|r&d||d< |d}|rGt	|
  }t| d}||d	< d
|d< |d }rR||d< | jr[|| j d| jv rbdnd}	| j |	 t| S )zKConstruct the upstream IdP authorization URL using stored transaction data.rY   r   )response_typerB   rZ   r?  rI   r   re   rM   r  rF   r8  rG   rL   r  r  )r   r@   r   r  r   r%  r   r   r  r  r  r   r   r
  r  r  r   r   )
rt   rA   rD  r  scopes_to_userM   r"  rC  rL   r  rV   rV   rW   rA    s*   
z(OAuthProxy._build_upstream_authorize_urlc                   s,   |j dkr| |I dH S | |I dH S )zFHandle consent page - dispatch to GET or POST handler based on method.r  N)method_submit_consent_show_consent_page)rt   r  rV   rV   rW   r    s   
zOAuthProxy._handle_consentc                   s
  ddl m} |jd}|stdddS | jj|dI dH }|s'tdddS | }| |d	 |d
 }t| 	|d}t| 	|d}||v rU| 
||}	t|	ddS ||v r}d|dp`dd}
d|d
 v rkdnd}t|d
  | t|
 ddS td}t d }||_||_| jj||ddI dH  ||d< ||d< | |d	 I dH }|rt|ddnd}t|jjdd}t||r|j}|j}|r|d jnd}|j}nd}d}d}t|d	 |d
 |dpg ||||||d	}t|}| j|d| |gdd |S ) z;Display consent page or auto-approve/deny based on cookies.r   )r~   rA   3<h1>Error</h1><p>Invalid or expired transaction</p>r  r   r   NrB   rC   MCP_APPROVED_CLIENTSMCP_DENIED_CLIENTSr  r  access_deniedrD   r   r   r?  r  r  r5  r9  r:  rN   rP   ro   fastmcp_serverrI   )	rB   rZ   rI   rA   rN   ro   rz   r{   r|   MCP_CONSENT_STATEr  )fastmcp.server.serverr~   r  r%  r:   r  rB  r  setr  rA  r'   r   r=  r>  r@  rN   rP   r0  r'  r/  appr?  r   r  iconssrcwebsite_urlr   r  r  )rt   r  r~   rA   	txn_modeltxn
client_keyapproveddeniedrE  r  seprN   rP   r&  ro   fastmcprz   r  r{   r|   r   r   rV   rV   rW   r    s   


zOAuthProxy._show_consent_pageRedirectResponse | HTMLResponsec                   s  |  I dH }t|dd}t|dd}t|dd}|s(tdddS | jj|d	I dH }|s:tdddS | }|d}t|d
pJd}	|rX||ksXt |	kr^tdddS | |d |d }
|dkrt	| 
|d}|
|vr}||
 | t|}| ||}t|dd}| j|d|dd | j|d| g dd |S |dkrt	| 
|d}|
|vr||
 | t|}d|dpdd}d|d v rdnd}|d  | t| }t|dd}| j|d|dd | j|d| g dd |S tdddS )zHHandle consent approval/denial, set cookies, and redirect appropriately.NrA   r   actionrN   r  r  r  r   rP   r   z5<h1>Error</h1><p>Invalid or expired consent token</p>rB   rC   approver  r  r  i3r  r  <   denyr  r  rD   r  r  r  z#<h1>Error</h1><p>Invalid action</p>)r   r@   r%  r:   r  rB  rJ   r@  r  r  r  addr  sortedrA  r'   r  r   )rt   r  r   rA   r  rN   r  r  expected_csrfr]   r  r   approved_b64rE  r   r  
denied_b64r  r  r  rV   rV   rW   r  2  st   



zOAuthProxy._submit_consent)&r   r@   r   r@   r   r@   r   r@   r   rE   r   r,   r   r   r   rE   r   r   r   r   r   rm   r   rm   r   r   r   rE   r   r   r   r   r   r   r   r   r   r   )rq   r  )rB   r@   rq   r$  )r(  r   rq   r   )r&  r   r4  r   rq   r@   )r&  r   r)  r@   rq   rH  )r&  r   r)  r   rq   r   )r&  r   rb   r@   rq   rd  )r&  r   rb   r   rI   rH   rq   r   )rY  r@   rq   r|  )rY  r  rq   r   r   )r  rE   rq   r  )r  r%   rq   r  )r2  r@   rq   r@   )rB   r@   rZ   r  rq   r@   )r  r@   rq   r@   )r}  r@   rq   r@   )r  r@   rq   rE   )r  r%   r  r@   rq   rH   )r  rH   rq   r@   )
r   r  r  r@   r  r@   r  r[  rq   r   )rA   r@   rD  r[   rq   r@   )r  r%   rq   r  )rQ   rR   rS   rT   r   r#  r)   r'  r3  rG  rN  rc  re  r{  r  r  r  r  r  r  r  r  r  r  r  r  rA  r  r  r  rw   rV   rV   ru   rW   r     sf    q  
0N. 
)
 
R
8.
g 
#
	





	

$
\r   )Nrx   NNNN)rB   r@   rZ   r@   rI   rH   rA   r@   rN   r@   ro   rE   ry   r@   rz   rE   r{   rE   r|   rE   r}   rE   rq   r@   )NNN)r   r@   r   r@   r   r   rz   rE   r{   rE   rq   r@   )mrT   
__future__r   r  r  r  r  r=  r@  r   typingr   r   r   urllib.parser   r   r  authlib.common.securityr	   !authlib.integrations.httpx_clientr
   cryptography.fernetr   key_value.aio.adapters.pydanticr   key_value.aio.protocolsr   key_value.aio.stores.diskr   !key_value.aio.wrappers.encryptionr   mcp.server.auth.handlers.tokenr   r   r   _SDKTokenHandlermcp.server.auth.json_responser   &mcp.server.auth.middleware.client_authr   mcp.server.auth.providerr   r   r   r   r   r   mcp.server.auth.routesr   mcp.server.auth.settingsr   r   mcp.shared.authr   r   pydanticr    r!   r"   r#   r$   starlette.requestsr%   starlette.responsesr&   r'   starlette.routingr(   typing_extensionsr)   r  r*   fastmcp.server.auth.authr+   r,   &fastmcp.server.auth.handlers.authorizer-   fastmcp.server.auth.jwt_issuerr.   r/   'fastmcp.server.auth.redirect_validationr0   fastmcp.utilities.loggingr1   fastmcp.utilities.uir2   r3   r4   r5   r6   r7   r8   r9   r:   rQ   r   r;   rU   r<   r>   r?   rX   r_   ri   rk   r   r   r   rV   rV   rV   rW   <module>   s     ,= !]1