Ë
    tPêj·K ã                   ó6  — U d dl Z d dlZd dlZd dlZd dlmZmZmZmZm	Z	 d dl
m
Z
mZmZ d dlmZmZmZmZmZ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 m!Z!m"Z"m#Z#m$Z$m%Z%m&Z&m'Z' d dl(m)Z)m*Z*m+Z+ d dl,m-Z- d dl.m/Z/ d dl0m1Z1 d dl2m3Z3m4Z4m5Z5 d dl6m7Z7 d dl8m9Z9 d dl:m;Z; d dl<m=Z= erd dl>m?Z@ d dl>mAZB d dlCmDZD  G d„ de«      ZE G d„ de«      ZF eGh d£«      ZHed   eId<   deJdz  deJdz  fd „ZK eGh d!£«      ZLed   eId"<    eGd#d$h«      ZMed   eId%<    eGh d&£«      ZNed   eId'<    G d(„ d)e«      ZO G d*„ d+eOd,¬-«      ZP G d.„ d/ed,¬-«      ZQd0eJdz  deJdz  fd1„ZRd2eSeJeTf   d3eSeJeTf   deSeJeTf   fd4„ZUd5eTdeVfd6„ZWd7eeSeJeJf      ddfd8„ZXd7ee!eSeJeJf   z     dz  ddfd9„ZYd:eTddfd;„ZZd7eJeeeJeJf      z  dz  d<eJde[eSeJeJf      dz  fd=„Z\	 	 d¾d>e$e&z  d?eVd@e]eJ   dz  deSeJef   fdA„Z^dBe=dCeJdz  de=fdD„Z_dEeJeeJeTf   z  deSeJeTf   fdF„Z`dGe-ddHfdI„ZadGe-ddJfdK„ZbdGe-ddLfdM„ZcdGe-ddNfdO„ZddGe-deFfdP„Ze	 d¿dGe-dQdRddSfdT„ZfdGe-dUeJddVfdW„ZgdGe-dUeJd>dXddYfdZ„ZhdGe-dd[fd\„ZidGe-dd]fd^„ZjdGe-d_eJdUeJdd`fda„Zk	 d¿dGe-dQdbddcfdd„ZldGe-d_eJdUeJdeeJddf
df„Zm	 d¿dGe-dQdgddhfdi„ZndBe=de=fdj„Zo	 d¿dGe-dkeJdz  de[e   fdl„ZpdGe-dUeJdedz  fdm„ZqdGe-dneeJ   de[e   fdo„ZrdGe-dpeJde[eJ   fdq„ZsdGe-dreJde[eJ   fds„ZtdGe-dte'de[e   fdu„ZudGe-dveJddwfdx„ZvdGe-dUeJddyfdz„ZwdGe-dUeJfd{„Zxd|„ Zy	 d¿dGe-dUeJd}eeJeJged   f   dz  dedz  fd~„ZzdGe-d>e$deJdefd€„Z{	 d¿dGe-d>e$deJd�e|dUeJdz  defd‚„Z}dGe-d�e|ddfdƒ„Z~dGe-dUeJd�e|dedz  fd„„Z	 d¿dGe-d>e&deJd@e]eJ   dz  dedz  f
d…„Z€dGe-dUeJdeTdz  fd†„Z�dGe-dUeJdBe=ddfd‡„Z‚dBdˆd‰eJdeJdz  fdŠ„ZƒdGe-deJd‰eJfd‹„Z„dŒeJdeJdz  fd�„Z…d_eJdUeJddfdŽ„Z†d�eJdz  dePdz  fd�„Z‡dŒeJdePdz  fd‘„ZˆdGe-d‰eJfd’„Z‰dGe-d‰eJfd“„ZŠdGe-d_eJdUeJd”eJddf
d•„Z‹dGe-d_eJdUeJdeJdz  fd–„ZŒdGe-d_eJdUeJdeVfd—„Z�dGe-d_eJdUeJddfd˜„ZŽ	 	 	 	 dÀdGe-d_eJdUeJd™eJdšeJdz  d›e|dz  dœe[eJ   dz  d�eVddfdž„Z�dÁdŸePd e|deVfd¡„Z�dGe-d_eJdUeJdePdz  fd¢„Z‘dGe-d_eJde[eP   fd£„Z’d¤eSeJeTf   d¥eJdeTfd¦„Z“d§eTde”eTd¨f   fd©„Z•	 d¿dGe-dUeJd}eeJeJged   f   dz  de|fdª„Z–dGe-d_eJd§d«dŸePdePdz  f
d¬„Z—	 d¿d_eJd§d«dŸePdz  dGe-dz  dePdz  f
d­„Z˜	 d¿d_eJdz  d§d«d®eeJePf   dz  deJdz  fd¯„Z™d°eJdz  de|dz  fd±„ZšdGe-d_eJde[eJ   fd²„Z›dGe-dUeJdeJdefd³„Zœ	 d¿dGe-dUeJdeJd´eJdz  def
dµ„Z�dGe-de#fd¶„ZždŒeJdeSeJeJf   fd·„ZŸdGe-d_eJdUeJdeSeJeJf   fd¸„Z dGe-d_eJdneeJ   deSeJeSeJeJf   f   fd¹„Z¡dGe-d_eJdUeJdºeSeJeJf   d»eeJ   deSeJeJf   fd¼„Z¢dGe-d_eJdUeJddfd½„Z£y)Âé    N)Ú	AwaitableÚCallableÚIterableÚMappingÚSequence)ÚdatetimeÚ	timedeltaÚtimezone)ÚTYPE_CHECKINGÚAnyÚFinalÚProtocolÚ	TypedDictÚcast)Úverbose_proxy_logger)Úuuid)Ú(MCP_PER_USER_TOKEN_EXPIRY_BUFFER_SECONDS)Úget_async_httpx_client)Ú#build_upstream_oauth2_token_request)	ÚLiteLLM_MCPServerTableÚMCPApprovalStatusÚ	MCPEnvVarÚMCPEnvVarScopeÚMCPSubmissionsSummaryÚNewMCPServerRequestÚSpecialMCPServerNameÚUpdateMCPServerRequestÚUserAPIKeyAuth)Ú_get_salt_keyÚdecrypt_value_helperÚencrypt_value_helper)ÚPrismaClient)ÚObjectPermissionRepository)ÚTableActions)ÚMCPServerOAuthClientRepositoryÚMCPServerRepositoryÚMCPUserCredentialsRepository)ÚTeamRepository)ÚVerificationTokenRepository)ÚhttpxSpecialProvider)ÚMCPCredentials)Úmodels)Útypes)Ú	MCPServerc                   ó,   — e Zd ZU ded<   dededefd„Zy)Ú_UserEnvVarsTransactionClientú5TableActions[prisma_db_models.LiteLLM_MCPUserEnvVars]Úlitellm_mcpuserenvvarsÚqueryÚargsÚreturnc              ‡   ó   K  — y ­w©N© )Úselfr3   r4   s      ún/home/oem/CyberBrain26-unified/.venv/lib/python3.12/site-packages/litellm/proxy/_experimental/mcp_server/db.pyÚexecute_rawz)_UserEnvVarsTransactionClient.execute_raw7   ó   è ø� ùó   ‚N)Ú__name__Ú
__module__Ú__qualname__Ú__annotations__ÚstrÚobjectÚintr;   r8   ó    r:   r0   r0   4   s   … ØSÓSàF sÐF°6ÐF¸cÔFrE   r0   c            	       ó6   — e Zd Zdefd„Zdededededz  fd„Zy)Ú_UserEnvVarsTransactionr5   c              ƒ   ó   K  — y ­wr7   r8   )r9   s    r:   Ú
__aenter__z"_UserEnvVarsTransaction.__aenter__;   r<   r=   Úexc_typeÚ	exc_valueÚ	tracebackNc              ƒ   ó   K  — y ­wr7   r8   )r9   rJ   rK   rL   s       r:   Ú	__aexit__z!_UserEnvVarsTransaction.__aexit__=   r<   r=   )r>   r?   r@   r0   rI   rC   ÚboolrN   r8   rE   r:   rG   rG   :   s)   „ ØDÐ"?ÓDài¨Ði¸6ÐiÈfÐiÐY]Ð`dÑYdÔirE   rG   >
   ÚissuerÚaudienceÚ	token_urlÚ
dcr_bridgeÚoauth2_flowÚregistration_urlÚauthorization_urlÚsubject_token_typeÚtoken_exchange_profileÚtoken_exchange_endpointzfrozenset[str]Ú_AUTH_FLOW_SCOPED_FIELDSÚvaluer5   c                 óL   — t        | t        «      sy | j                  «       xs d S r7   )Ú
isinstancerB   Ústrip)r[   s    r:   Ú_blank_to_noner_   P   s    € Ü�eœSÔ!ØØ�;‰;‹=Ò ˜DÐ rE   >   rQ   rW   rX   rY   Ú_TOKEN_EXCHANGE_COLUMN_FIELDSÚtrue_passthroughÚoauth_delegateÚ_CLIENT_FORWARDED_AUTH_TYPES>   Ú
expires_inÚaccess_tokenÚrefresh_tokenÚ_MINTED_TOKEN_CREDENTIAL_FIELDSc                   ó   — e Zd ZU eed<   y)Ú_OAuthCredentialAccessTokenre   N)r>   r?   r@   rB   rA   r8   rE   r:   ri   ri   p   s   … ØÔrE   ri   c                   óP   — e Zd ZU eed<   eed<   eed<   eed<   ee   ed<   eed<   y)ÚOAuthCredentialPayloadÚtyperf   Ú
expires_atÚconnected_atÚscopesÚ	server_idN)r>   r?   r@   rB   rA   Úlistr8   rE   r:   rk   rk   t   s'   … Ø
ƒIØÓØƒOØÓØ�‰IÓØ„NrE   rk   F)Útotalc                   ó6   — e Zd ZU eed<   eed<   eed<   eed<   y)Ú_OAuthTokenRefreshResponsere   rf   rd   ÚscopeN)r>   r?   r@   rB   rA   rD   r8   rE   r:   rt   rt   }   s   … ØÓØÓØƒOØ„JrE   rt   Ú	auth_typec                 ó   — | t         v ry| S )a)  Collapse the client-forwarded modes to one credential class; every other auth_type is its own
    class. Used so credential handling keys off whether the stored-credential shape actually changed,
    not off a raw auth_type inequality that treats true_passthrough<->oauth_delegate as a full reset.Úclient_forwarded)rc   )rv   s    r:   Ú_credential_auth_classry   „   s   € ð Ô0Ñ0Ø!ØÐrE   ÚmergedÚ	new_credsc                 ó„   — d|vrd|vr| S | j                  «       D ��ci c]  \  }}|t        vs||v sŒ||“Œ c}}S c c}}w )zÎWhen the update rotates the client, drop stale minted token keys it did not itself set, so an old
    app's access/refresh token never rides forward under the new client. A no-op when no client key changed.Ú	client_idÚclient_secret)Úitemsrg   )rz   r{   Úkeyr[   s       r:   Ú%_drop_stale_minted_on_client_rotationr�   �   sT   € ð ˜)Ñ#¨¸yÑ(HØˆà%+§\¡\£^÷Ù!�s˜E°sÔBaÑ7aÐehÐluÒeuˆˆU‰
óð ùó s   ž<³<ru   c                 ó6   — | t         j                  k7  xr | dk7  S )z�``scope="user"`` entries are placeholders the user fills in; everything
    else (including a missing scope) is an admin-supplied global value.Úuser)r   rƒ   )ru   s    r:   Ú_is_global_env_var_scoper„   —   s   € ð ”N×'Ñ'Ñ'Ò;¨E°V©OÐ;rE   Úenv_varsc                 óŒ   — | D ]?  }t        |j                  d«      «      sŒ|j                  d«      }|sŒ2t        |«      |d<   ŒA y)aU  Encrypt ``scope="global"`` env var values in place before persisting.

    Global values hold admin-supplied secrets (API keys, passwords) that get
    interpolated into headers, so they are encrypted at rest like credentials
    and the per-user ``values_b64`` column. Per-user placeholders are not
    secrets and are stored verbatim.
    ru   r[   N)r„   Úgetr!   )r…   Úentryr[   s      r:   Ú_encrypt_global_env_var_valuesr‰   �   sF   € ð ò 9ˆÜ'¨¯	©	°'Ó(:Ô;ØØ—	‘	˜'Ó"ˆÚÜ1°%Ó8ˆE�'ŠNñ9rE   c                 ó†  — | sy| D ]¹  }t        |t        «      }|r|j                  d«      nt        |dd«      }t	        |«      sŒ?|r|j                  d«      nt        |dd«      }|sŒbt        |ddd¬«      }|€8|r|j                  d«      nt        |dd«      }t        j                  d	|«       d
}|r||d<   Œ³||_        Œ» y)aÂ  Decrypt ``scope="global"`` env var values in place after reading the DB.

    Accepts ``MCPEnvVar`` models (``LiteLLM_MCPServerTable``) or plain dicts
    (raw rows / deserialized JSON). Global values are always stored encrypted,
    so a value that no longer decrypts (e.g. after a salt-key change) is dropped
    and a warning is logged rather than forwarding the ciphertext into upstream
    ``${NAME}`` headers, where it would silently fail.
    Nru   r[   Úmcp_global_env_varÚdebugF©r[   r€   Úexception_typeÚreturn_original_valueÚnamezsMCP global env var %s failed to decrypt (LITELLM_SALT_KEY changed?); dropping it so ciphertext is not sent upstreamÚ )	r]   Údictr‡   Úgetattrr„   r    r   Úwarningr[   )r…   rˆ   Úis_dictru   r[   Ú	decryptedr�   s          r:   Údecrypt_global_env_var_valuesr—   ­   sÖ   € ñ ØØò $ˆÜ˜U¤DÓ)ˆÙ&-�—	‘	˜'Ô"´7¸5À'È4Ó3PˆÜ'¨Ô.ØÙ&-�—	‘	˜'Ô"´7¸5À'È4Ó3PˆÙØÜ(ØØ$Ø"Ø"'ô	
ˆ	ð ÐÙ(/�5—9‘9˜VÔ$´W¸UÀFÈDÓ5QˆDÜ ×(Ñ(ðLàôð
 ˆIÙØ&ˆE�'ŠNà#ˆE�Kñ3$rE   Úrowc                 ó,  — t        | dd«      }|€yt        |t        «      r5	 t        j                  |«      }t        |t        «      sy	 t        | d|«       t        |«       y# t        j
                  t        f$ r Y yw xY w# t        t        f$ r Y Œ<w xY w)a“  Decrypt ``scope="global"`` env var values on a row returned by Prisma create/update.

    Prisma may hand back ``env_vars`` either as a parsed list (the common case for
    JSONB columns) or as a raw JSON string (observed for some write paths). The
    in-place decrypt helper only mutates iterables of dicts/models, so a string
    payload would silently skip decryption and ciphertext would leak into the
    registry via ``add_server``/``update_server`` (which trust the caller).
    Parse the string back to a list so the in-place decrypt actually runs, and
    write the decrypted list back onto the row so downstream consumers see plain
    values.
    r…   N)r“   r]   rB   ÚjsonÚloadsÚJSONDecodeErrorÚ	TypeErrorrq   ÚsetattrÚAttributeErrorr—   )r˜   r…   s     r:   Ú!_decrypt_env_vars_on_returned_rowr    Ô   s™   € ô �s˜J¨Ó-€HØÐØÜ�(œCÔ ð	Ü—z‘z (Ó+ˆHô ˜(¤DÔ)Øð	Ü�C˜ XÔ.ô " (Õ+øô ×$Ñ$¤iÐ0ò 	Ùð	ûô ¤	Ð*ò 	Ùð	ús#   ¢A" Á	B Á"A>Á=A>ÂBÂBÚnew_encryption_keyc                 óä  — | syt        | t        «      r	 t        j                  | «      }|sy| }|D �cg c]  }t        |«      ‘Œ }}d}|D ]z  }t        |j                  d«      «      sŒ|j                  d«      }|sŒ2t        |ddd¬«      }|€&t        j                  d|j                  d	«      «       Œit        ||¬
«      |d<   d}Œ| |r|S dS # t        j                  t
        f$ r Y yw xY wc c}w )a»  Re-encrypt ``scope="global"`` env var values for master-key rotation.

    Each global value is decrypted with the current salt key and re-encrypted
    under ``new_encryption_key``. Returns the rebuilt list when at least one
    value was rotated, else ``None`` so the caller can skip the DB write. A
    value that fails to decrypt is left untouched (and logged) so a corrupt
    entry is preserved for recovery rather than overwritten.
    NFru   r[   r‹   rŒ   r�   zWrotate_mcp_server_credentials_master_key: could not decrypt global env var %s, skippingr�   ©r¡   T)r]   rB   rš   r›   rœ   r�   r’   r„   r‡   r    r   r”   r!   )	r…   r¡   ÚentriesÚvÚrebuiltÚrotatedrˆ   r[   r–   s	            r:   Ú _reencrypt_global_env_var_valuesr¨   ñ   s  € ñ Øä�(œCÔ ð	Ü—j‘j Ó*ˆGñ ØàˆØ'.Ö/ !”d˜1•gÐ/€GÐ/Ø€GØò ˆÜ'¨¯	©	°'Ó(:Ô;ØØ—	‘	˜'Ó"ˆÙØÜ(ØØ$Ø"Ø"'ô	
ˆ	ð ÐÜ ×(Ñ(ØiØ—	‘	˜&Ó!ôð Ü-¨iÐL^Ô_ˆˆg‰Ø‰ð'ñ( ˆ7Ð' 4Ð'øô9 ×$Ñ$¤iÐ0ò 	Ùð	üò 0s   •C ³C-ÃC*Ã)C*ÚdataÚexclude_unsetÚ
fields_setc                 óÌ  — ddl m} |rq|€| j                  «       }| j                  d¬«      }|j	                  d«      €d|vr|j                  dd«       d|v r
|d   €g |d<   dD ]  }||v sŒ||   �Œi ||<   Œ n&| j                  d¬	«      }d|vrt        | dd«      |d<   |j	                  d
«      }|�Pt        D ]!  }|j                  |d«      }|€Œ||vsŒ|||<   Œ# t        |t        «       ¬«      |d
<    ||d
   «      |d
<   |j	                  d«      � ||d   «      |d<   |j	                  d«      }	|	�.|	D �
cg c]  }
t        |
«      ‘Œ }}
t        |«        ||«      |d<   |j	                  d«      � ||d   «      |d<   |j	                  d«      � ||d   «      |d<   d|v r ||d   xs i «      |d<   d|v r ||d   xs i «      |d<   |st        | dd«      |d<   |S c c}
w )a~  
    Helper function to prepare MCP server data for database operations.
    Handles JSON field serialization for mcp_info and env fields.

    Args:
        data: NewMCPServerRequest or UpdateMCPServerRequest object
        exclude_unset: When True, only fields the caller explicitly provided are
            included. Used for partial updates (PUT /v1/mcp/server) so omitted
            fields keep their existing DB value instead of being silently reset
            to a Pydantic schema default. ``exclude_none`` is not enough here:
            non-Optional fields (e.g. ``transport=MCPTransport.sse``,
            ``mcp_access_groups=[]``, ``allow_all_keys=False``) are backfilled
            with their default when omitted, and a non-None default survives the
            ``exclude_none`` filter and overwrites the row.

    Returns:
        Dict with properly serialized JSON fields
    r   ©Ú
safe_dumpsNT)rª   ÚaliasÚallowed_tools)Útool_name_to_display_nameÚtool_name_to_description)Úexclude_noneÚcredentials©r´   Úencryption_keyÚstatic_headersr…   Úmcp_infoÚenvr±   r²   Úis_byokF)Ú*litellm.litellm_core_utils.safe_json_dumpsr®   r«   Ú
model_dumpr‡   Úpopr“   r`   Úencrypt_credentialsr   r’   r‰   )r©   rª   r«   r®   Ú	data_dictÚjson_map_fieldr´   Úte_fieldÚ
blob_valuer…   r¥   Úserialized_env_varss               r:   Ú_prepare_mcp_server_datarÄ   !  sj  € õ. Fñ ØÐØŸ™Ó*ˆJØ—O‘O°$�OÓ7ˆ	ð
 �=‰=˜Ó!Ð)¨g¸ZÑ.GØ�M‰M˜' 4Ô(ð ˜iÑ'¨I°oÑ,FÐ,NØ)+ˆI�oÑ&ð
ò 	/ˆNð  Ò*¨y¸Ñ/HÑ/PØ,.�	˜.Ò)ñ	/ð —O‘O°�OÓ6ˆ	à˜)Ñ#Ü!(¨¨w¸Ó!=ˆI�gÑð #Ÿ™ }Ó5€KØÐô
 6ò 	1ˆHØ$Ÿ™¨°4Ó8ˆJØÑ%¨(¸)Ò*CØ&0�	˜(Ò#ð	1ô $7À;Ô_lÓ_nÔ#oˆ	�-Ñ Ù#-¨i¸Ñ.FÓ#Gˆ	�-Ñ ð
 ‡}�}Ð%Ó&Ð2Ù&0°Ð;KÑ1LÓ&Mˆ	Ð"Ñ#ð ;D¿-¹-È
Ó:S€HØÐØ7?Ö%@°!¤d¨1¥gÐ%@ÐÐ%@Ü&Ð':Ô;Ù *Ð+>Ó ?ˆ	�*Ñà‡}�}�ZÓ Ð,Ù *¨9°ZÑ+@Ó Aˆ	�*Ñà‡}�}�UÓÐ'Ù% i°Ñ&6Ó7ˆ	�%Ñà" iÑ/Ù1;¸IÐFaÑ<bÒ<hÐfhÓ1iˆ	Ð-Ñ.Ø! YÑ.Ù0:¸9ÐE_Ñ;`Ò;fÐdfÓ0gˆ	Ð,Ñ-ñ Ü& t¨Y¸Ó>ˆ	�)ÑàÐùò- &As   Ä9G!r´   r¶   c                 óð  — | j                  d«      }|�t        ||¬«      | d<   | j                  d«      }|�t        ||¬«      | d<   | j                  d«      }|�t        ||¬«      | d<   | j                  d«      }|�t        ||¬«      | d<   | j                  d«      }|�t        ||¬«      | d<   | j                  d«      }|�t        ||¬«      | d<   | j                  d«      }|�t        ||¬«      | d<   | S )	NÚ
auth_value)r[   r¡   r}   r~   Úclient_private_keyÚaws_access_key_idÚaws_secret_access_keyÚaws_session_token)r‡   r!   )	r´   r¶   rÆ   r}   r~   rÇ   rÈ   rÉ   rÊ   s	            r:   r¾   r¾   Œ  sA  € Ø#Ÿ™¨Ó5€JØÐÜ$8ØØ-ô%
ˆ�LÑ!ð #—‘ {Ó3€IØÐÜ#7ØØ-ô$
ˆ�KÑ ð 'Ÿ?™?¨?Ó;€MØÐ Ü';ØØ-ô(
ˆ�OÑ$ð !,§¡Ð0DÓ EÐØÐ%Ü,@Ø$Ø-ô-
ˆÐ(Ñ)ð
  +Ÿ™Ð/BÓCÐØÐ$Ü+?Ø#Ø-ô,
ˆÐ'Ñ(ð $/§?¡?Ð3JÓ#KÐØÐ(Ü/CØ'Ø-ô0
ˆÐ+Ñ,ð  +Ÿ™Ð/BÓCÐØÐ$Ü+?Ø#Ø-ô,
ˆÐ'Ñ(ð
 ÐrE   Úblobc                 ój   — t        | t        «      rt        j                  | «      }|S t	        | «      }|S r7   )r]   rB   rš   r›   r’   )rË   Úparsed_blobs     r:   Ú!_credentials_blob_to_mutable_dictrÎ   ¼  s5   € Ü@JÈ4ÔQTÔ@U¬D¯J©J°tÓ,<€KØÐô \`Ð`dÓ[e€KØÐrE   Úprisma_clientz5TableActions[prisma_db_models.LiteLLM_MCPServerTable]c                 ó0   — t        | «      j                  }|S r7   )r&   Útable©rÏ   rÑ   s     r:   Ú_mcp_server_table_actionsrÓ   Á  s   € ô K^Ð^kÓJl×JrÑJr€EØ€LrE   z8TableActions[prisma_db_models.LiteLLM_VerificationToken]c                 ó0   — t        | «      j                  }|S r7   )r)   rÑ   rÒ   s     r:   Ú!_verification_token_table_actionsrÕ   È  s#   € ô NiØóNç�eð 
ð €LrE   z0TableActions[prisma_db_models.LiteLLM_TeamTable]c                 ó0   — t        | «      j                  }|S r7   )r(   rÑ   rÒ   s     r:   Ú_team_table_actionsr×   Ñ  s   € ô FTÐTaÓEb×EhÑEh€EØ€LrE   z;TableActions[prisma_db_models.LiteLLM_MCPServerOAuthClient]c                 ó0   — t        | «      j                  }|S r7   )r%   rÑ   rÒ   s     r:   Ú_oauth_client_table_actionsrÙ   Ø  s#   € ô QoØóQç�eð 
ð €LrE   c                 ó:   — | j                   j                  «       }|S r7   )ÚdbÚtx)rÏ   Úmanagers     r:   Ú_db_transaction_managerrÞ   á  s   € Ø.;×.>Ñ.>×.AÑ.AÓ.C€GØ€NrE   Úwherez7prisma_db_types.LiteLLM_MCPServerTableWhereInput | Nonez1Sequence[prisma_db_models.LiteLLM_MCPServerTable]c              ƒ   óT   K  — t        | «      j                  |¬«      ƒ d {  –—† S 7 Œ­w©N©rß   )rÓ   Ú	find_many©rÏ   rß   s     r:   Ú_db_find_mcp_server_rowsrå   æ  s'   è ø€ ô +¨=Ó9×CÑCÈ%ÐCÓP×PÐPÐPúó   ‚(¡&¢(rp   z.prisma_db_models.LiteLLM_MCPServerTable | Nonec              ƒ   óX   K  — t        | «      j                  d|i¬«      ƒ d {  –—† S 7 Œ­w)Nrp   râ   )rÓ   Úfind_unique©rÏ   rp   s     r:   Ú_db_find_mcp_server_rowrê   í  s-   è ø€ ô +¨=Ó9×EÑEÈ[ÐZcÐLdÐEÓe×eÐeÐeús   ‚!*£(¤*z1prisma_db_types.LiteLLM_MCPServerTableUpdateInputz'prisma_db_models.LiteLLM_MCPServerTablec              ƒ   ó~   K  — t        | «      j                  d|i|¬«      ƒ d {  –—† }|€t        d|› �«      ‚|S 7 Œ­w)Nrp   ©rß   r©   z'MCP server not found, passed server_id=)rÓ   ÚupdateÚ
ValueError)rÏ   rp   r©   r˜   s       r:   Ú_db_update_mcp_server_rowrï   ó  s^   è ø€ ô
 HaÐanÓGo×GvÑGvØ˜IÐ&Øð Hwó H÷ B€Cð €{ÜÐBÀ9À+ÐNÓOÐOØ€JðBús   ‚"=¤;¥=z9TableActions[prisma_db_models.LiteLLM_MCPUserCredentials]c                 ó0   — t        | «      j                  }|S r7   )r'   rÑ   rÒ   s     r:   Ú_user_credential_actionsrñ     s#   € ô OkØóOç�eð 
ð €LrE   r1   c                 ó2   — | j                   j                  }|S r7   )rÛ   r2   rÒ   s     r:   Ú_user_env_var_actionsró   
  s   € ð KX×JZÑJZ×JqÑJq€EØ€LrE   Úuser_idz2prisma_db_models.LiteLLM_MCPUserCredentials | Nonec              ƒ   ó^   K  — t        | «      j                  d||dœi¬«      ƒ d {  –—† S 7 Œ­w)NÚuser_id_server_id©rô   rp   râ   )rñ   rè   ©rÏ   rô   rp   s      r:   Ú_db_find_user_credential_rowrù     s=   è ø€ ô *¨-Ó8×DÑDØ"°ÀiÑ$PÐQð Eó ÷ ð ð ús   ‚$-¦+§-z;prisma_db_types.LiteLLM_MCPUserCredentialsWhereInput | Nonez5Sequence[prisma_db_models.LiteLLM_MCPUserCredentials]c              ƒ   óT   K  — t        | «      j                  |¬«      ƒ d {  –—† S 7 Œ­wrá   )rñ   rã   rä   s     r:   Ú_db_find_user_credential_rowsrû     s'   è ø€ ô *¨-Ó8×BÑBÈÐBÓO×OÐOÐOúræ   Úcredential_b64c              ƒ   ót   K  — t        | «      j                  d||dœi|||dœd|idœ¬«      ƒ d {  –—†  y 7 Œ­w)Nrö   r÷   )rô   rp   rü   rü   ©Úcreaterí   rì   )rñ   Úupsert)rÏ   rô   rp   rü   s       r:   Ú_db_upsert_user_credential_rowr     sV   è ø€ ô # =Ó
1×
8Ñ
8Ø"°ÀiÑ$PÐQð #Ø&Ø"0ñð
 (¨Ð8ñ
ð 9ó 
÷ 
ò 
ús   ‚.8°6±8z7prisma_db_types.LiteLLM_MCPUserEnvVarsWhereInput | Nonez1Sequence[prisma_db_models.LiteLLM_MCPUserEnvVars]c              ƒ   óT   K  — t        | «      j                  |¬«      ƒ d {  –—† S 7 Œ­wrá   )ró   rã   rä   s     r:   Ú_db_find_user_env_var_rowsr  0  s'   è ø€ ô ' }Ó5×?Ñ?ÀeÐ?ÓL×LÐLÐLúræ   c                 óŠ   — g d¢}|D ]9  }| j                  |«      }|€Œt        |t        «      sŒ(t        ||dd¬«      | |<   Œ; | S )zNDecrypt all secret fields in an MCPCredentials dict using the global salt key.)rÆ   r}   r~   rÇ   rÈ   rÉ   rÊ   rŒ   Tr�   ©r‡   r]   rB   r    )r´   Úsecret_fieldsÚfieldr[   s       r:   Údecrypt_credentialsr  7  sZ   € ò€Mð ò ˆØ—‘ Ó&ˆØÑ¤¨E´3Õ!7Ü!5ØØØ&Ø&*ô	"ˆK˜Òðð ÐrE   Úapproval_statusc              ƒ   ó  K  — |�d|indddiddt         j                  iigi}t        | |«      ƒ d{  –—† }|D �cg c]%  }t        j                  |j                  «       «      ‘Œ' }}|D ]  }t        |j                  «       Œ |S 7 ŒRc c}w ­w)a×  
    Returns mcp servers from the db, optionally filtered by approval_status.
    Pass approval_status=None to return every server except drafts, which back the admin OAuth
    session flow, are addressable only by their own server_id, and must never appear in a listing.
    NULL approval_status predates the approval workflow, so those rows are kept explicitly rather
    than dropped by a bare inequality, which SQL evaluates as NULL and would silently hide them.
    Nr	  ÚORÚnot)r   Údraftrå   r   Úmodel_validater¼   r—   r…   )rÏ   r	  rß   Úmcp_serversÚ
mcp_serverÚtablesrÑ   s          r:   Úget_all_mcp_serversr  P  s§   è ø€ ð Ð&ð 
˜OÑ,ð Ð'¨Ð.Ð1BÀUÔL]×LcÑLcÐDdÐ0eÐfÐgð 
ô  8¸ÀuÓM×M€KàfqÖrÐXbÔ+×:Ñ:¸:×;PÑ;PÓ;RÕSÐr€FÐrØò 6ˆÜ% e§n¡nÕ5ð6à€Mð Núârùs    ‚0B²B³B»*BÁ%!BÂBc              ƒ   ó°   K  — t        | |«      ƒ d{  –—† }|€yt        j                  |j                  «       «      }t	        |j
                  «       |S 7 ŒA­w)z@
    Returns the matching mcp server from the db iff exists
    N)rê   r   r  r¼   r—   r…   )rÏ   rp   r  rÑ   s       r:   Úget_mcp_serverr  j  sP   è ø€ ô 6°mÀYÓO×O€JØÐØÜ)×8Ñ8¸×9NÑ9NÓ9PÓQ€EÜ! %§.¡.Ô1Ø€Lð Pús   ‚A’A“AAÚ
server_idsc              ƒ   ó  K  — t        | «      j                  dd|ii¬«      ƒ d{  –—† }g }|D ]K  }t        j                  |j	                  «       «      }t        |j                  «       |j                  |«       ŒM |S 7 ŒX­w)zJ
    Returns the matching mcp servers from the db with the server_ids
    rp   Úinrâ   N)rÓ   rã   r   r  r¼   r—   r…   Úappend)rÏ   r  Ú_mcp_serversÚfinal_mcp_serversÚ_mcp_serverrÑ   s         r:   Úget_mcp_serversr  v  s•   è ø€ ô TmØóTç�ià˜$ 
Ð+ð
ð ó ÷N€Lð >@ÐØ#ò (ˆÜ&×5Ñ5°k×6LÑ6LÓ6NÓOˆÜ% e§n¡nÔ5Ø× Ñ  Õ'ð(ð
 ÐðNús   ‚#B ¥A>¦AB Útokenc              ƒ   ó¶   K  — t        | «      j                  d|iddi¬«      ƒ d{  –—† }g }|�"|j                  �|j                  j                  }|xs g S 7 Œ0­w)zH
    Returns the mcp servers from the db for the verification token
    r  Úobject_permissionT©rß   ÚincludeN)rÕ   rè   r  r  )rÏ   r  Úverification_token_recordr  s       r:   Ú$get_mcp_servers_by_verificationtokenr#  Š  s{   è ø€ ô 0°Ó>×JÑJà�Uð
ð   ð
ð	 Kó ÷ 	ð ð %'€KØ Ð,Ð1J×1\Ñ1\Ð1hØ/×AÑA×MÑMˆØÒ˜"Ðð	úó   ‚$A¦A§1AÚteam_idc              ƒ   ó¶   K  — t        | «      j                  d|iddi¬«      ƒ d{  –—† }g }|�"|j                  �|j                  j                  }|xs g S 7 Œ0­w)z=
    Returns the mcp servers from the db for the team id
    r%  r  Tr   N)r×   rè   r  r  )rÏ   r%  Úteam_recordr  s       r:   Úget_mcp_servers_by_teamr(  Ÿ  s|   è ø€ ô DWÐWdÓCe×CqÑCqà�wð
ð   ð
ð	 Dró D÷ >€Kð %'€KØÐ ;×#@Ñ#@Ð#LØ!×3Ñ3×?Ñ?ˆØÒ˜"Ðð>úr$  rƒ   c              ƒ   ó†  K  — t        «       }g }|j                  r|t        | |j                  «      ƒ d{  –—† }|j                  |«       t        j
                  |v r;|j                  �/t        | |j                  «      ƒ d{  –—† }|j                  |«       t        |«      dkD  rt        | |«      ƒ d{  –—† }|S 7 Œ†7 Œ;7 Œ
­w)zÉ
    Get all the mcp servers filtered by the given user has access to.

    Following Least-Privilege Principle - the requestor should only be able to see the mcp servers that they have access to.
    Nr   )
ÚsetÚapi_keyr#  rí   r   Úall_team_serversr%  r(  Úlenr  )rÏ   rƒ   Úmcp_server_idsr  Útoken_mcp_serversÚteam_mcp_serverss         r:   Úget_all_mcp_servers_for_userr1  ²  s¸   è ø€ ô '*£e€NØ€Kð ‡|‚|Ü)MÈmÐ]a×]iÑ]iÓ)j×#jÐØ×ÑÐ/Ô0ô  ×0Ñ0°NÑBÀtÇ|Á|ÐG_Ü,CÀMÐSW×S_ÑS_Ó,`×&`ÐØ×!Ñ!Ð"2Ô3ä
ˆ>Ó˜QÒÜ+¨M¸>ÓJ×JˆàÐð $køð
 'aøð Kús4   ‚2C´B;µACÂB=Â2CÂ4B?Â5CÂ=CÂ?CÚmcp_server_idz8Sequence[prisma_db_models.LiteLLM_ObjectPermissionTable]c              ƒ   ó|   K  — t        | «      j                  j                  dd|iidddœ¬«      ƒ d{  –—† }|S 7 Œ­w)z‰
    Get all the object permissions records and the associated team and verficiationtoken records that have access to the mcp server
    r  ÚhasT)ÚteamsÚverification_tokensr   N)r#   rÑ   rã   )rÏ   r2  Úobject_permission_recordss      r:   Ú$get_objectpermissions_for_mcp_serverr8  Ï  sZ   è ø€ ô )¨Ó7×=Ñ=×GÑGà˜E =Ð1ð
ð Ø#'ñ
ð	 Hó ÷ 	ð ð %Ð$ð	ús   ‚1<³:´<z4Sequence[prisma_db_models.LiteLLM_VerificationToken]c              ƒ   ó|   K  — t        | «      j                  j                  dd|ii¬«      ƒ d{  –—† }|€g S |S 7 Œ
­w)zE
    Get all the virtual keys that have access to the mcp server
    r  r4  râ   N)r)   rÑ   rã   )rÏ   rp   Úvirtual_keyss      r:   Úget_virtualkeys_for_mcp_serverr;  ä  sX   è ø€ ô *¨-Ó8×>Ñ>×HÑHà˜E 9Ð-ð
ð Ió ÷ 	ð ð ÐØˆ	ØÐð	ús   ‚-<¯:°<c              ƒ   ó   K  — y­w)z-
    Remove the mcp server from the team
    Nr8   ré   s     r:   Údelete_mcp_server_from_teamr=  ÷  r<   r=   c               ƒ   ó   K  — y­w)z4
    Remove the mcp server from the virtual key
    Nr8   r8   rE   r:   Ú!delete_mcp_server_from_virtualkeyr?  ý  r<   r=   Úinvalidate_token_cachec              ƒ   ó´  K  — t        | «      j                  j                  d|i¬«      ƒ d{  –—† }|�ºg }	 t        | «      j	                  d|i¬«      ƒ d{  –—† }|D �cg c]  }|j
                  ‘Œ }}t        | «      dft        | «      dft        | «      dffD ]"  \  }}		 |j                  d|i¬«      ƒ d{  –—†  Œ$ |r,|€d	d
lm}
 |
j                  }|D ]  } |||«      ƒ d{  –—†  Œ |S 7 ŒÂ7 Œšc c}w # t        $ r!}t        j                  d||«       Y d}~Œ©d}~ww xY w7 Œl# t        $ r"}t        j                  d||	|«       Y d}~Œ³d}~ww xY w7 Œn­w)aŸ  
    Delete the mcp server from the db by server_id

    The server-row delete is the commit point. Per-user credential and env var
    rows have no FK cascade, so they are cleaned up afterwards on a best-effort
    basis: a transient failure there leaves only orphaned rows pointing at a
    now-missing server and must not turn a successful delete into a
    caller-visible error. Each table is cleaned independently so a failure on one
    still attempts the other.

    Each enumerated credential row's user also gets their cached per-user token
    invalidated (legacy cache + v2 store, via invalidate_token_cache, defaulting
    to the manager's shared invalidation): the caches are keyed by
    (user_id, server_id), so without this a re-created server reusing the same
    server_id would serve tokens minted for the deleted server until TTL.

    Returns the deleted mcp server record if it exists, otherwise None
    rp   râ   NzaMCP server %s deleted but per-user credential enumeration failed; cached tokens expire by TTL: %sÚ
credentialzenv varzOAuth clientzhMCP server %s deleted but per-user %s cleanup failed; orphaned rows can be removed on a later delete: %sr   ©Úglobal_mcp_server_manager)r&   rÑ   Údeleterñ   rã   rô   Ú	Exceptionr   r”   ró   rÙ   Údelete_manyÚ9litellm.proxy._experimental.mcp_server.mcp_server_managerrD  Ú!invalidate_user_oauth_token_cache)rÏ   rp   r@  Údeleted_serverÚcredential_user_idsÚcredential_rowsr˜   ÚeÚmodelÚlabelrD  rô   s               r:   Údelete_mcp_serverrP    s­  è ø€ ô. #6°mÓ"D×"JÑ"J×"QÑ"Qà˜ð
ð #Ró #÷ €Nð
 Ð!Ø)+Ðð
	Ü[sØó\ç‰i˜{¨IÐ6ˆiÓ7÷V8ˆOð ;JÖ"J°3 3§;£;Ð"JÐÐ"Jô & mÓ4°lÐCÜ" =Ó1°9Ð=Ü(¨Ó7¸ÐHð
ò 	‰LˆE�5ð
	Ø×'Ñ'¨{¸IÐ.FÐ'ÓG×GÑGð	ñ Ø%Ð-õð *C×)dÑ)dÐ&Ø.ò A�Ù,¨W°iÓ@×@Ñ@ðAàÐðUøðV8úò #KøÜò 	Ü ×(Ñ(ØsØØ÷ñ ûð	úð HùÜò Ü$×,Ñ,ðIàØØ÷ñ ûðúð  Aús¤   ‚+E­C0®E· C9 ÁC2ÁC9 Á C4Á3C9 Á5,EÂ"D(Â9D&Â:D(Â>)EÃ'EÃ(	EÃ2C9 Ã4C9 Ã9	D#ÄDÄEÄD#Ä#EÄ&D(Ä(	EÄ1EÅ	EÅEÅEÚ
touched_byc              ƒ   ó  K  — |j                   €"t        t        j                  «       «      |_         t	        |«      }||d<   ||d<   t        | «      j                  j                  |¬«      ƒ d{  –—† }t        |«       |S 7 Œ­w)z2
    Create a new mcp server record in the db
    NÚ
created_byÚ
updated_by)r©   )	rp   rB   r   Úuuid4rÄ   r&   rÑ   rÿ   r    )rÏ   r©   rQ  r¿   Únew_mcp_servers        r:   Úcreate_mcp_serverrW  G  s„   è ø€ ð ‡~�~ÐÜœTŸZ™Z›\Ó*ˆŒô 0°Ó5€Ið )€IˆlÑØ(€IˆlÑä:MÈmÓ:\×:bÑ:b×:iÑ:iØð ;jó ;÷ 5€Nô & nÔ5ØÐð5ús   ‚A,BÁ.B Á/BÚttl_secondsc              ƒ   óN  K  — |xs+ |j                   xs t        t        j                  «       «      }t	        | |«      ƒ d{  –—†  t        | |«      ƒ d{  –—† }|�#t        j                  |j                  «       «      S |j                  |t        j                  dœ¬«      }	 t        | ||«      ƒ d{  –—† S 7 Œv7 Œd7 Œ# t        $ r[ t        | |«      ƒ d{  –—†7  }|�|j                  t        j                  k7  r‚ t        j                  |j                  «       «      cY S w xY w­w)aé  
    Persist a short-lived draft row backing the admin OAuth "Authorize & Fetch Token" flow.

    The draft lives in the database rather than in process memory so that the /register,
    /authorize and /token legs resolve it whichever worker or replica accepts each request.

    Writing is strictly create-if-absent. Any existing row for the id is returned untouched, which
    covers both a live draft for this same session and a real server the edit form is
    re-authorizing against its own id, where writing a draft would collide on the primary key.
    Each click of Authorize mints a fresh id, so nothing is lost by never overwriting, and it is
    what makes concurrent callers sharing one id safe rather than mutually destructive.
    N)rp   r	  )rí   )rp   rB   r   rU  Ú _prune_expired_draft_mcp_serversrê   r   r  r¼   Ú
model_copyr   r  rW  rF  r	  )	rÏ   r©   rQ  rX  rp   Údraft_idÚexistingÚdraft_payloadÚraceds	            r:   Úcreate_draft_mcp_serverr`  _  s  è ø€ ð&  ÒF 4§>¡>ÒF´S¼¿¹»Ó5F€HÜ
*¨=¸+Ó
F×FÐFä3°MÀ8ÓL×L€HØÐô &×4Ñ4°X×5HÑ5HÓ5JÓKÐKàŸ?™?ÀÔ]n×]tÑ]tÑ2u˜?Óv€Mð	IÜ& }°mÀZÓP×PÐPð GøàLøð QùÜò Iô 5°]ÀHÓM×MÐMˆØˆ=˜E×1Ñ1Ô5F×5LÑ5LÒLØÜ%×4Ñ4°U×5EÑ5EÓ5GÓHÒHðIüsg   ‚?D%ÁB8ÁD%ÁB:ÁAD%Â#B> Â3B<Â4B> Â7D%Â:D%Â<B> Â>D"ÃCÃAD"ÄD%Ä!D"Ä"D%c              ƒ   óV  K  — t        j                  t        j                  «      t	        t        d|«      ¬«      z
  }t        | dt        j                  i¬«      ƒ d{  –—† }|D ]=  }|j                  €Œ|j                  |k  sŒ t        | |j                  «      ƒ d{  –—†  Œ? y7 ŒG7 Œ	­w)av  Drop drafts already past ``ttl_seconds``, so abandoned OAuth sessions do not accumulate.

    Runs on each draft write rather than on a schedule, mirroring the in-memory cache this
    replaces, which pruned on every store. Expired drafts are unreadable by then anyway, so the
    only thing at stake is row count, and the work is bounded by how often admins authorize.
    é   ©Úsecondsr	  râ   N)r   Únowr
   Úutcr	   Úmaxrå   r   r  Ú
updated_atrP  rp   )rÏ   rX  ÚcutoffÚdraftsr˜   s        r:   rZ  rZ  ‰  s˜   è ø€ ô —L‘L¤§¡Ó.´Ä3ÀqÈ+ÓCVÔ1WÑW€Fô 3ØØ Ô"3×"9Ñ"9Ð:ô÷ €Fð ò Bˆð �>‰>Ñ%¨#¯.©.¸6Ó*AÜ# M°3·=±=ÓA×AÑAñBð	øð Bús0   ‚AB)ÁB%ÁB)Á4B)ÂB)ÂB'ÂB)Â'B)c              ƒ   óT  K  — t        j                  t        j                  «      t	        t        d|«      ¬«      z
  }t        | |t        j                  d|idœ¬«      ƒ d{  –—† }|syt        j                  |d   j                  «       «      }t        |j                  «       |S 7 ŒD­w)a  
    Return the draft row for ``server_id`` if it has not yet aged past ``ttl_seconds``, else None.

    Age is enforced in the query rather than by a sweeper so an expired draft is unreadable the
    moment it lapses, regardless of which process last ran a cleanup.
    rb  rc  Úgte)rp   r	  rh  râ   Nr   )r   re  r
   rf  r	   rg  rå   r   r  r   r  r¼   r—   r…   )rÏ   rp   rX  ri  Ú
draft_rowsrÑ   s         r:   Úget_draft_mcp_serverrn  Ÿ  s�   è ø€ ô —L‘L¤§¡Ó.´Ä3ÀqÈ+ÓCVÔ1WÑW€FÜ6Øà"Ü0×6Ñ6Ø  &˜/ñ
ô÷ €Jñ Øä)×8Ñ8¸ÀA¹×9QÑ9QÓ9SÓT€EÜ! %§.¡.Ô1Ø€Lðús   ‚AB(Á!B&Á"AB(c              ƒ   ó  K  — ddl m} t        |d|¬«      }d}d|v xr |d   du}t        t        |j                  «       z  «      }d|v xr |d   du}	d|v }
|j                  s|s|s|	s|
rt        | |j                  «      ƒ d{  –—† }t        |j                  xr/ |xr+ t        |j                  «      t        |j                  «      k7  «      }t        |	xr |xr |j                  |d   k7  «      }|rt        t        |dd«      «      nd}t        |
xr# |duxr t        |j                  d«      «      |k7  «      }|r	d|vrd|d<   |s|s|r;|j                  t        D �ci c]  }||vs||   t        ||d«      k(  r|d“Œ c}«       |r�d|vr}|�{|j                   rot#        |j                   «      }t        |j                  «       z  rCt        D ]/  }|j%                  |d«      }|€Œ||vsŒt        ||d«      �Œ+|||<   Œ1  ||«      |d<   d|v r‹|d   �†|r„|j                   rx|svt#        |j                   «      }t#        |d   «      }t'        i |¥|¥|«      }t        D ]/  }|j%                  |d«      }|€Œ||vsŒt        ||d«      �Œ+|||<   Œ1  ||«      |d<   ||d	<   d|v r|d   €dd
lm}  |d«      |d<   t-        | «      j.                  j                  d|j                  i|¬«      ƒ d{  –—† }t1        |«       |S 7 �Œ|c c}w 7 Œ­w)z2
    Update a new mcp server record in the db
    r   r­   T)rª   r«   Nr´   ÚurlrP   rT  )ÚJsonrp   rì   )r»   r®   rÄ   rO   r`   Úkeysrv   rê   rp   ry   rp  r_   r“   r‡   rí   rZ   r´   rÎ   r½   r�   Úprismarq  r&   rÑ   r    )rÏ   r©   rQ  r«   r®   r¿   r]  Úhas_credentialsÚexplicit_te_writeÚurl_providedÚissuer_providedÚauth_type_changedÚurl_changedÚ
old_issuerÚissuer_changedr  Úexisting_credsrÁ   Úlegacy_valuer{   rz   rq  Úupdated_mcp_servers                          r:   Úupdate_mcp_serverr  ¹  s’  è ø€ õ Fô 0°ÀDÐU_Ô`€Ið €HØ*¨iÐ7Ò`¸IÀmÑ<TÐ\`Ð<`€Oô  $Ô$AÀIÇNÁNÓDTÑ$TÓUÐØ 9Ð,ÒM°¸5Ñ1AÈÐ1M€LØ%¨Ð2€OØ‡~‚~™Ñ,=ÁÑQ`Ü0°ÀÇÁÓO×Oˆä#Ø�‰ò 	aØò	aä" 8×#5Ñ#5Ó6Ô:PÐQU×Q_ÑQ_Ó:`Ñ`ó Ðô ˜lÒ\¨xÒ\¸H¿L¹LÈIÐV[ÑL\Ñ<\Ó]€KÙMUœ¤w¨x¸À4Ó'HÔIÐ[_€JÜ ØÒl˜J¨dÐ2Òl´~ÀiÇmÁmÐT\ÓF]Ó7^ÐblÑ7ló€Nñ
 ˜]°)Ñ;Ø#'ˆ	�-Ñ á™K©>ð
 	×Ñô 6öàØ 	Ñ)¨Y°uÑ-=ÄÈÐSXÐZ^ÓA_Ò-_ð �t‘òô	
ñ ˜]°)Ñ;ÀÐ@TÐYa×YmÒYmÜ:¸8×;OÑ;OÓPˆÜ(¨>×+>Ñ+>Ó+@Ò@Ü9ò 7�Ø-×1Ñ1°(¸DÓA�ØÑ+°À	Ò0IÌgÐV^Ð`hÐjnÓNoÑNwØ*6�I˜hÒ'ð7ñ (2°.Ó'AˆI�mÑ$ð
 ˜	Ñ! i°Ñ&>Ð&JÙ˜×,Ò,ñ %Ü!BÀ8×CWÑCWÓ!X�Ü#DÀYÈ}ÑE]Ó#^�	ô !FÐFeÈÐFeÐ[dÐFeÐgpÓ q�ô !>ò ;�HØ#)§:¡:¨h¸Ó#=�Là$Ñ0Ø$¨IÒ5Ü# H¨h¸Ó=ÑEà.:˜	 (Ò+ð;ñ ,6°fÓ+=�	˜-Ñ(ð )€IˆlÑð
 ˜	Ñ! i°Ñ&>Ð&FÝá#'¨£:ˆ	�-Ñ äEXÐYfÓEg×EmÑEm×EtÑEtØ˜DŸN™NÐ+Øð Fuó F÷ @Ðô
 &Ð&8Ô9ØÐðA Pûò2ðB@úsX   ‚BL
ÂL ÂCL
Å !LÆA$L
Ç&L
Ç+L
Ç9A;L
É5L
É:L
ÊA&L
Ë.LË/L
ÌL
c              ƒ   óv   K  — t        | «      j                  d|i¬«      ƒ d{  –—† }|€y|j                  S 7 Œ­w)a_  Read the persisted (encrypted) DCR OAuth client blob for a server from the
    server-scoped store, or None. Config.yaml-declared servers have no
    LiteLLM_MCPServerTable row, so their dynamically registered client lives here keyed
    by server_id. The returned value is the raw credentials blob for
    ``_get_persisted_dcr_credentials`` to parse.rp   râ   N)rÙ   rè   r´   )rÏ   rp   r˜   s      r:   Ú'get_mcp_server_oauth_client_credentialsr�  6  sI   è ø€ ô NiØóNç�k˜ iÐ0€kÓ1÷H2€Cð €{ØØ�?‰?ÐðH2ús   ‚!9£7¤9c              ƒ   óÄ   K  — ddl m} t        t        d
i |¤Žt	        «       ¬«      } ||«      }t        | «      j                  d|i||dœd|idœ¬«      ƒ d	{  –—†  y	7 Œ­w)až  Persist a server's dynamically registered OAuth client (RFC 7591 DCR) in the
    server-scoped store keyed by server_id, independent of any LiteLLM_MCPServerTable row.
    client_id/client_secret are encrypted at rest with the same salt key used for the
    server row's credentials blob, so ``_apply_persisted_dcr_credentials`` decrypts them the
    same way regardless of which store a server's client came from.r   r­   rµ   rp   )rp   r´   r´   rþ   rì   Nr8   )r»   r®   r¾   r+   r   rÙ   r   )rÏ   rp   r´   r®   Ú	encryptedrË   s         r:   Ú*upsert_mcp_server_oauth_client_credentialsr„  D  sm   è ø€ õ Fä*´~Ñ7TÈÑ7TÔerÓetÔu€IÙ˜YÓ'€DÜ
% mÓ
4×
;Ñ
;Ø˜IÐ&à$-¸dÑCØ$ dÐ+ñ
ð <ó ÷ ò ús   ‚AA ÁAÁA z!str | Mapping[str, object] | NoneÚnew_master_keyc                 óˆ   — | syddl m} t        | «      }t        t	        t
        |«      ¬«      }t        ||¬«      } ||«      S )aH  Decrypt an at-rest MCP credentials blob with the current key and re-encrypt it under
    new_master_key, returning the serialized blob or None when there is nothing to rotate. Shared by
    every table that stores an encrypted MCP credentials blob so a master-key rotation covers them
    uniformly and cannot silently skip one.Nr   r­   )r´   rµ   )r»   r®   rÎ   r  r   r+   r¾   )r´   r…  r®   Ú
creds_dictr–   rƒ  s         r:   Ú_reencrypt_mcp_credentials_blobrˆ  Y  s@   € ñ ØÝEä9¸+ÓF€JÜ*´t¼NÈJÓ7WÔX€IÜ*°yÐQ_Ô`€IÙ�iÓ Ð rE   c              ƒ   ó‚  K  — ddl m} t        | «      ƒ d {  –—† }d}|D ]�  }i }t        |j                  |«      }|�||d<   t        |j                  |«      }	|	� ||	«      |d<   |sŒH||d<   t        | «      j                  d|j                  i|¬«      ƒ d {  –—†  |dz  }Œƒ t        | «      j                  «       ƒ d {  –—† }
d}|
D ]R  }t        |j                  |«      }|€Œt        | «      j                  d|j                  id|i¬«      ƒ d {  –—†  |dz  }ŒT t        j                  d	||«       y 7 �Œ7 Œ 7 Œz7 Œ*­w)
Nr   r­   r´   r…   rT  rp   rì   rb  zarotate_mcp_server_credentials_master_key: rotated %d MCP server row(s) and %d OAuth-client row(s))r»   r®   rå   rˆ  r´   r¨   r…   rÓ   rí   rp   rÙ   rã   r   Úinfo)rÏ   rQ  r…  r®   r  Úupdatedr  Úupdate_dataÚrotated_credentialsÚrotated_env_varsÚoauth_clientsÚoauth_updatedÚoauth_clients                r:   Ú(rotate_mcp_server_credentials_master_keyr’  j  s–  è ø€ ÝEä7¸ÓF×F€Kà€GØ!ò ˆ
Ø&(ˆä=¸j×>TÑ>TÐVdÓeÐØÐ*Ø)<ˆK˜Ñ&ä;¸J×<OÑ<OÐQ_Ó`ÐØÐ'Ù&0Ð1AÓ&BˆK˜
Ñ#áØà$.ˆ�LÑ!Ü'¨Ó6×=Ñ=Ø 
× 4Ñ 4Ð5Øð >ó 
÷ 	
ð 	
ð 	�1‰‰ð'ô* [vØó[ç�iƒk÷U€Mð €MØ%ò ˆÜ=¸l×>VÑ>VÐXfÓgÐØÐ&ØÜ)¨-Ó8×?Ñ?Ø × 6Ñ 6Ð7ØÐ!4Ð5ð @ó 
÷ 	
ð 	
ð 	˜Ñ‰ðô ×ÑØkØØõðM Gùð$	
øðUøð	
úsG   ‚D?—D6˜B D?ÂD9Â'D?Ã D;ÃAD?ÄD=Ä$D?Ä9D?Ä;D?Ä=D?Ústoredc                 óÂ   — t        | ddd¬«      }|�|S 	 t        j                  | «      j                  «       S # t        j
                  t        t        t        f$ r Y yw xY w)aN  Read back a value persisted in ``LiteLLM_MCPUserCredentials.credential_b64``.

    Tries nacl decryption first (current write format).  Falls back to a
    plain ``urlsafe_b64decode`` for rows persisted by older code that wrote
    the credential without encryption.  Returns ``None`` when neither path
    yields a valid string.
    Úmcp_user_credentialrŒ   Fr�   N)	r    Úbase64Úurlsafe_b64decodeÚdecodeÚbinasciiÚErrorÚUnicodeDecodeErrorrî   r�   )r“  r–   s     r:   Ú_decode_user_credentialrœ  š  sg   € ô ,ØØ!ØØ#ô	€Ið ÐØÐðÜ×'Ñ'¨Ó/×6Ñ6Ó8Ð8øÜ�N‰NÔ.´
¼IÐFò Ùðús   •"8 ¸#AÁAc                 ó2   — t        j                  d| |«       y)zMLog the one credential state that otherwise reads as "user never authorized".z°MCP user credential for user=%s server=%s could not be decrypted (likely written under a previous LITELLM_SALT_KEY); the user is treated as not connected and must re-authorize.N)r   r”   r÷   s     r:   Ú_warn_undecryptable_credentialrž  °  s   € ä× Ñ ð	bàØõ	rE   Údecodedc                 ó¬   — | €y	 t        j                  | «      }t	        |t
        «      r|j                  d«      dk(  r|S y# t        t        f$ r Y yw xY w)aZ  Return the OAuth2 payload dict if ``decoded`` holds one, else ``None``.

    A row is considered an OAuth2 credential iff its decoded value parses as
    a JSON object with ``"type": "oauth2"``.  Plain BYOK credentials (which
    share the same column) decode to a non-JSON string and return ``None``.

    Callers that need to tell an unreadable row from a readable non-OAuth2 one
    pass the result of :func:`_decode_user_credential` so a single decode
    answers both questions: ``None`` there means the value can be neither
    decrypted nor base64-decoded, so no caller can ever recover it.
    Nrl   Úoauth2)rš   r›   rî   r�   r]   r’   r‡   )rŸ  Úparseds     r:   Ú_parse_oauth_payloadr£  º  s\   € ð €ØðÜ—‘˜GÓ$ˆô �&œ$Ô F§J¡J¨vÓ$6¸(Ò$BØˆØøô	 œ	Ð"ò Ùðús   …A ÁAÁAc                 ó*   — t        t        | «      «      S )zAReturn the OAuth2 payload dict held in ``stored``, else ``None``.)r£  rœ  )r“  s    r:   Ú_decode_oauth_payloadr¥  Ò  s   € äÔ 7¸Ó ?Ó@Ð@rE   c              ƒ   ó°  K  — t        | «      ƒ d{  –—† }d}d}|D ]›  }t        |j                  «      }|€1t        j                  d|j
                  |j                  «       |dz  }ŒKt        ||¬«      }t        | «      j                  d|j
                  |j                  dœid|i¬	«      ƒ d{  –—†  |dz  }Œ� t        j                  d
||«       y7 ŒÀ7 Œ%­w)a}  Re-encrypt every ``LiteLLM_MCPUserCredentials`` row with ``new_master_key``.

    Reads each ``credential_b64`` with the current salt key (falling back to
    legacy plain base64 for unmigrated rows) and writes it back encrypted
    under the new master key.  Rows that are unreadable under both paths
    are logged and skipped so one corrupt row does not abort the rotation.
    Nr   zirotate_mcp_user_credentials_master_key: could not decode credential for user_id=%s server_id=%s, skippingrb  r£   rö   r÷   rü   rì   zErotate_mcp_user_credentials_master_key: rotated %d row(s), skipped %d)rû   rœ  rü   r   r”   rô   rp   r!   rñ   rí   rŠ  ©rÏ   r…  Úrowsr§   Úskippedr˜   Ú	plaintextÚre_encrypteds           r:   Ú&rotate_mcp_user_credentials_master_keyr¬  ×  s÷   è ø€ ô 6°mÓD×D€DØ€GØ€GØò ˆÜ+¨C×,>Ñ,>Ó?ˆ	ØÐÜ ×(Ñ(ðCà—‘Ø—‘ô	ð �q‰LˆGØÜ+¨IÈ.ÔYˆÜ& }Ó5×<Ñ<à#Ø"Ÿ{™{Ø!$§¡ñ&ðð # LÐ1ð =ó 
÷ 	
ð 	
ð 	�1‰‰ð+ô, ×ÑØOØØõð3 Eøð	
ús"   ‚C‘C’BCÂ.CÂ/$CÃCc              ƒ   ó¸  K  — t        | «      ƒ d{  –—† }d}d}|D ]Ÿ  }t        |j                  ddd¬«      }|€1t        j                  d|j
                  |j                  «       |dz  }ŒOt        ||¬	«      }t        | «      j                  d
|j
                  |j                  dœid|i¬«      ƒ d{  –—†  |dz  }Œ¡ t        j                  d||«       y7 ŒÄ7 Œ%­w)ab  Re-encrypt every ``LiteLLM_MCPUserEnvVars`` row with ``new_master_key``.

    Reads each ``values_b64`` blob with the current salt key and writes it back
    encrypted under the new master key. Rows that fail to decrypt are logged and
    skipped so one corrupt row does not abort the rotation nor overwrite values
    that may still be recoverable.
    Nr   Úmcp_user_env_varsrŒ   Fr�   zerotate_mcp_user_env_vars_master_key: could not decrypt env vars for user_id=%s server_id=%s, skippingrb  r£   rö   r÷   Ú
values_b64rì   zBrotate_mcp_user_env_vars_master_key: rotated %d row(s), skipped %d)r  r    r¯  r   r”   rô   rp   r!   ró   rí   rŠ  r§  s           r:   Ú#rotate_mcp_user_env_vars_master_keyr°  ÿ  s   è ø€ ô 3°=ÓA×A€DØ€GØ€GØò ˆÜ(Ø—.‘.Ø#Ø"Ø"'ô	
ˆ	ð ÐÜ ×(Ñ(ØwØ—‘Ø—‘ôð
 �q‰LˆGØÜ+¨IÈ.ÔYˆÜ# MÓ2×9Ñ9à#Ø"Ÿ{™{Ø!$§¡ñ&ðð  Ð-ð :ó 
÷ 	
ð 	
ð 	�1‰‰ð3ô4 ×ÑØLØØõð; Bøð&	
ús"   ‚C‘C’B CÂ2CÂ3$CÃCrB  c              ƒ   óR   K  — t        |«      }t        | |||«      ƒ d{  –—†  y7 Œ­w)z.Store a user credential for a BYOK MCP server.N)r!   r  )rÏ   rô   rp   rB  Úencodeds        r:   Ústore_user_credentialr³  +  s&   è ø€ ô *¨*Ó5€GÜ
(¨¸ÀÈGÓ
T×TÒTús   ‚'Ÿ% 'c              ƒ   óh   K  — t        | ||«      ƒ d{  –—† }|€yt        |j                  «      S 7 Œ­w)z2Return credential for a user+server pair, or None.N)rù   rœ  rü   ©rÏ   rô   rp   r˜   s       r:   Úget_user_credentialr¶  7  s8   è ø€ ô 4°MÀ7ÈIÓV×V€CØ
€{ØÜ" 3×#5Ñ#5Ó6Ð6ð Wús   ‚2“0”2c              ƒ   ó@   K  — t        | ||«      ƒ d{  –—† }|duS 7 Œ­w)z@Return True if the user has a stored credential for this server.N)rù   rµ  s       r:   Úhas_user_credentialr¸  D  s)   è ø€ ô 4°MÀ7ÈIÓV×V€CØ�dˆ?Ðð Wús   ‚“”	c              ƒ   ó`   K  — t        | «      j                  d||dœi¬«      ƒ d{  –—†  y7 Œ­w)z:Delete the user's stored credential for a BYOK MCP server.rö   r÷   râ   N)rñ   rE  rø   s      r:   Údelete_user_credentialrº  N  s7   è ø€ ô # =Ó
1×
8Ñ
8Ø"°ÀiÑ$PÐQð 9ó ÷ ò ús   ‚$.¦,§.re   rf   rd   ro   Úskip_byok_guardc              ƒ   ód  K  — d}|�>t        j                  t        j                  «      t	        |¬«      z   j                  «       }d|t        j                  t        j                  «      j                  «       dœ}	|r||	d<   |r||	d<   |r||	d<   |sft        | ||«      ƒ d{  –—† }
|
�t        |
j                  «      nd}|
�6t        |«      €+|rt        d|› d	|› d
�«      ‚t        j                  d||«       t        t        j                  |	«      «      }t!        | |||«      ƒ d{  –—†  y7 ŒŠ7 Œ­w)zøPersist an OAuth2 access token for a user+server pair.

    The payload is JSON-serialised and stored encrypted in the same
    ``credential_b64`` column used by BYOK.  A ``"type": "oauth2"`` key
    differentiates it from plain BYOK API keys.
    Nrc  r¡  )rl   re   rn   rf   rm   ro   zExisting credential for user z and server zA could not be verified as an OAuth2 token. Refusing to overwrite.zÆstore_user_oauth_credential: existing credential for user=%s server=%s could not be decrypted (likely written under a previous LITELLM_SALT_KEY); replacing it with the newly authorized OAuth2 token.)r   re  r
   rf  r	   Ú	isoformatrù   rœ  rü   r£  rî   r   r”   r!   rš   Údumpsr  )rÏ   rô   rp   re   rf   rd   ro   r»  rm   Úpayloadr]  rŸ  r²  s                r:   Ústore_user_oauth_credentialrÀ  \  sG  è ø€ ð" "€JØÐÜ—l‘l¤8§<¡<Ó0´9ÀZÔ3PÑP×[Ñ[Ó]ˆ
ð Ø$Ü Ÿ™¤X§\¡\Ó2×<Ñ<Ó>ñ.€Gñ
 Ø#0ˆ�Ñ ÙØ *ˆ�ÑÙØ"ˆ�Ññ
 Ü <¸]ÈGÐU^Ó _×_ˆØMUÐMaÔ0°×1HÑ1HÔIÐgkˆØÐÔ$8¸Ó$AÐ$Iñ Ü Ø3°G°9¸LØ �kð "-ð.óð ô
 !×(Ñ(ð1ð Øôô *¬$¯*©*°WÓ*=Ó>€GÜ
(¨¸ÀÈGÓ
T×TÑTð/ `øð. Uús%   ‚BD0Â!D,Â"BD0Ä&D.Ä'D0Ä.D0ÚcredÚbuffer_secondsc                 ó>  — | j                  d«      }|sy	 t        j                  |«      }|j                  € |j	                  t
        j                  ¬«      }t        j                  t
        j                  «      t        |¬«      z   |kD  S # t        t        f$ r Y yw xY w)aÍ  Return True if the OAuth2 credential's access_token has expired.

    Checks the ``expires_at`` ISO-format string stored in the credential payload.
    Returns False when ``expires_at`` is absent or unparseable (treat as non-expired).
    With ``buffer_seconds`` > 0, a token that is still valid but expires within the
    buffer is also treated as expired, so callers can refresh proactively instead of
    handing back a token that may lapse mid-request.
    rm   F©Útzinforc  )r‡   r   ÚfromisoformatrÅ  Úreplacer
   rf  re  r	   rî   r�   )rÁ  rÂ  rm   Úexp_dts       r:   Úis_oauth_credential_expiredrÉ  ›  s…   € ð Ÿ™ Ó.€JÙØðÜ×'Ñ'¨
Ó3ˆØ�=‰=Ð Ø—^‘^¬8¯<©<�^Ó8ˆFÜ�|‰|œHŸL™LÓ)¬I¸nÔ,MÑMÐPVÑVÐVøÜœ	Ð"ò Ùðús   –A3B
 Â
BÂBc              ƒ   óš   K  — t        | ||«      ƒ d{  –—† }|€yt        |j                  «      }|€t        ||«       t	        |«      S 7 Œ5­w)zGReturn the decoded OAuth2 payload dict for a user+server pair, or None.N)rù   rœ  rü   rž  r£  )rÏ   rô   rp   r˜   rŸ  s        r:   Úget_user_oauth_credentialrË  °  sQ   è ø€ ô 4°MÀ7ÈIÓV×V€CØ
€{ØÜ,¨S×-?Ñ-?Ó@€GØ€Ü& w°	Ô:Ü Ó(Ð(ð Wús   ‚A“A	”6Ac              ƒ   ó  K  — t        | d|i«      ƒ d{  –—† }g }|D ]]  }t        |j                  «      }|€t        ||j                  «       t        |«      }|€Œ>|j                  |d<   |j                  |«       Œ_ |S 7 Œj­w)zHReturn all OAuth2 credential payloads for a user, tagged with server_id.rô   Nrp   )rû   rœ  rü   rž  rp   r£  r  )rÏ   rô   r¨  Úresultsr˜   rŸ  r¿  s          r:   Úlist_user_oauth_credentialsrÎ  À  s�   è ø€ ô 6°mÀiÐQXÐEYÓZ×Z€DØ35€GØò  ˆÜ)¨#×*<Ñ*<Ó=ˆØˆ?Ü*¨7°C·M±MÔBÜ& wÓ/ˆØˆ?ØØ"Ÿ}™}ˆ�ÑØ�‰�wÕð ð €Nð [ús   ‚B”A?•A+BÚcredsr  c                 óf   — | j                  |«      }t        |t        «      s|S t        ||dd¬«      S )z¶Return one credential field decrypted with the global salt key; non-string and legacy
    plaintext values come back unchanged (decrypt_value_helper returns the original on failure).rŒ   Tr�   r  )rÏ  r  r[   s      r:   Ú_decrypted_credential_fieldrÑ  Ô  s:   € ð —9‘9˜UÓ#€EÜ�eœSÔ!ØˆÜØØØØ"ô	ð rE   Úserver.c                 óè  — t        | dd«      }t        |t        «      r	 t        j                  |«      }n|}t        |t        «      r|ni }t        | dd«      t        | dd«      t        | dd«      t        | dd«      t        | dd«      t        | dd«      t        | d	d«      t        | d
d«      t        |d«      t        |d«      |j                  d«      |j                  d«      fS # t
        $ r d}Y Œ¹w xY w)aa  The upstream-OAuth-token-determining fields of an MCP server: the resource/audience (url, or
    spec_path for OpenAPI servers, plus the RFC 8707 upstream_resource sent on the authorize and
    token legs), the OAuth mode/grant (auth_type, oauth2_flow), the authorization-server endpoints,
    and the OAuth client + scopes. Mirrors the dashboard's getOAuthAuthorizationIdentity. When any
    of these change on a server update, previously stored per-user tokens were minted for the old
    identity and are stale. Excludes transport and delegate_auth_to_upstream, which do not affect
    what token is minted (RFC 8693).

    client_id/client_secret are compared decrypted: stored values are NaCl-encrypted with a fresh
    nonce on every write, so comparing ciphertext would flag every routine save as an identity
    change and purge tokens that are still valid.r´   Nrp  Ú	spec_pathrv   rT   rP   rV   rR   rU   r}   r~   ro   Úupstream_resource)	r“   r]   rB   rš   r›   rî   r’   rÑ  r‡   )rÒ  rÏ  r¢  r‡  s       r:   Úmcp_oauth_token_identityrÖ  â  sö   € ô ˜6 =°$Ó7€EÜ�%œÔð	Ü/3¯z©z¸%Ó/@‰Fð ˆÜ5?ÀÌÔ5M©6ÐSU€Jä�˜˜tÓ$Ü�˜ TÓ*Ü�˜ TÓ*Ü�˜ tÓ,Ü�˜ $Ó'Ü�Ð+¨TÓ2Ü�˜ TÓ*Ü�Ð*¨DÓ1Ü# J°Ó<Ü# J°Ó@Ø�‰�xÓ Ø�‰Ð*Ó+ðð øô ò 	ØŠFð	ús   ŸC# Ã#C1Ã0C1c              ƒ   óô  K  — t        | d|i«      ƒ d{  –—† }|D �cg c]  }t        |j                  «      €Œ|‘Œ }}|syt        | «      j	                  |d|D �cg c]  }|j
                  ‘Œ c}idœ¬«      ƒ d{  –—† }|€ddlm} |j                  }|D ]  } ||j
                  |«      ƒ d{  –—†  Œ |t        |«      k7  r!t        j                  d||t        |«      «       |S 7 ŒÓc c}w c c}w 7 Œw7 ŒE­w)	aç  Delete every stored per-user OAuth token for a server and invalidate each user's cached
    token everywhere it can be served from (the legacy per-user token cache and the v2 per-user OAuth
    token store), so no user keeps a token minted for a superseded configuration. Called when a server
    update changes a mint-relevant field (see mcp_oauth_token_identity). Returns the number of rows
    removed.

    LiteLLM_MCPUserCredentials also stores BYOK API keys in the same column; only rows whose payload
    decodes as an OAuth2 credential (see _decode_oauth_payload) are deleted, because a config change
    only invalidates minted tokens, never a user's own stored key. Rows are therefore deleted per
    (user_id, server_id) pair rather than by a blanket server_id filter. An OAuth row inserted while
    the purge runs for a user not yet enumerated survives; a re-auth completing in the window for an
    already-enumerated user is deleted along with the stale row (the pair delete cannot tell them
    apart), which costs that user one extra re-auth and nothing else.

    invalidate_token_cache is injectable for tests; it defaults to the manager's shared
    invalidate_user_oauth_token_cache, the single invalidation point for per-user tokens.rp   Nr   r  )rp   rô   râ   rC  z�MCP server %s: purge removed %d OAuth credential row(s) but %d were enumerated; row(s) were deleted concurrently during the purge)rû   r¥  rü   rñ   rG  rô   rH  rD  rI  r-  r   r”   )rÏ   rp   r@  r¨  r˜   Ú
oauth_rowsÚdeleted_countrD  s           r:   Ú'purge_user_oauth_credentials_for_serverrÚ    s  è ø€ ô* 6°mÀkÐS\ÐE]Ó^×^€DØ(,Öf Ô0EÀc×FXÑFXÓ0YÑ0ešÐf€JÐfÙØÜ!9¸-Ó!H×!TÑ!TØ%°4ÐQ[Ö9\È#¸#¿+»+Ò9\Ð2]Ñ^ð "Uó "÷ €Mð Ð%õ	
ð ";×!\Ñ!\Ðàò =ˆÙ$ S§[¡[°)Ó<×<Ñ<ð=àœ˜J›Ò'Ü×$Ñ$ð@àØÜ�
‹Oô	
ð Ðð1 _úÚfùò :]ðøð 	=úsN   ‚C8”C(•C8�C*¸C*¼C8ÁC/Á.C8Á<C4Á=3C8Â0C6Â18C8Ã*C8Ã6C8r.   c           
   ƒ   ó°  K  — |j                  d«      }t        |dd«      xs t        |dd«      }t        |dd«      }t        |dd«      }t        |dd«      }|st        j                  d	||«       y|st        j                  d
|«       y	 t	        |t        |dd«      ||¬«      }	d|dœ|	j
                  ¥}
t        t        j                  ¬«      }|j                  |ddi|	j                  ¥|
¬«      ƒ d{  –—† }|j                  «        |j                  «       }|j                  d«      }|st        j                  d||«       yd}|j                  d«      }	 |�t        |«      nd}|j                  d«      xs |}|j                  d«      }t%        |t&        «      r|r|j)                  «       ndxs |j                  d«      }t+        | ||||||d¬«      ƒ d{  –—†  t        j,                  d||«       t/        | ||«      ƒ d{  –—† S 7 �Œ# t        $ r"}t        j                  d|||«       Y d}~yd}~ww xY w# t         t"        f$ r Y Œæw xY w7 Œv7 ŒL­w)uó  Attempt to refresh a per-user OAuth2 token using its stored refresh_token.

    POSTs to ``server.effective_token_url`` with ``grant_type=refresh_token``.

    On success: persists the new credential via ``store_user_oauth_credential``
    and returns the updated payload dict.
    On failure (network error, invalid_grant, missing refresh_token, â€¦): logs a
    warning and returns ``None`` â€” the caller is responsible for clearing the
    stale credential and triggering re-authentication.
    rf   Úeffective_token_urlNrR   rp   r‘   r}   r~   zGrefresh_user_oauth_token: no refresh_token stored for user=%s server=%sz?refresh_user_oauth_token: server=%s has no token_url configuredÚtoken_endpoint_auth_method)Úauth_methodr}   r~   )Ú
grant_typerf   )Úllm_providerÚAcceptzapplication/json)Úheadersr©   zJrefresh_user_oauth_token: refresh request failed for user=%s server=%s: %sre   zSrefresh_user_oauth_token: token response missing access_token for user=%s server=%srd   ru   ro   T)rÏ   rô   rp   re   rf   rd   ro   r»  z?refresh_user_oauth_token: refreshed token for user=%s server=%s)r‡   r“   r   rŒ   r   Úbodyr   r*   ÚOauth2CheckÚpostrâ  Úraise_for_statusrš   rF  r”   rD   r�   rî   r]   rB   ÚsplitrÀ  rŠ  rË  )rÏ   rô   rÒ  rÁ  rf   rR   rp   r}   r~   Útoken_requestÚ
token_dataÚasync_clientÚresponserã  Úexcre   rd   Úraw_expiresÚnew_refresh_tokenÚ	raw_scopero   s                        r:   Úrefresh_user_oauth_tokenrð  7  s¢  è ø€ ð  (,§x¡x°Ó'@€MÜ#*¨6Ð3HÈ$Ó#OÒ#uÔSZÐ[aÐcnÐptÓSu€IÜ# F¨K¸Ó<€IÜ#*¨6°;ÀÓ#E€IÜ'.¨v°ÈÓ'M€MáÜ×"Ñ"ØUØØô	
ð
 ÙÜ×"Ñ"ØMØô	
ð ðÜBØÜ Ð(DÀdÓKØØ'ô	 
ˆð *Ø*ñ-
ð × Ñ ð-
ˆ
ô
 5ÔBV×BbÑBbÔcˆØ ,× 1Ñ 1ØØÐ1ÐK°]×5JÑ5JÐKØð !2ó !
÷ 
ˆð
 	×!Ñ!Ô#Ø2:·-±-³/ˆð '+§h¡h¨~Ó&>€LÙÜ×$Ñ$ØaØØô	
ð
 à!€JØŸ™ ,Ó/€KðØ)4Ð)@”S˜Ô%Àdˆ
ð
 ,0¯8©8°OÓ+DÒ+UÈÐà—x‘x Ó(€IÜ5?À	Ì3Ô5OÑT] 	§¡Ô 1Ðcgò  Ðlp×ltÑltØóm€Fô &Ø#ØØØ!Ø'ØØØô	÷ 	ð 	ô ×ÑØIØØôô
 +¨=¸'À9ÓM×MÐMðq
úô ò Ü×$Ñ$ØXØØØô		
ô ûðûô, ”zÐ"ò Ùðúð	øð  Nús†   ‚BIÂA*H Ã5HÃ6$H Ä>IÅH= Å(A3IÇIÇ+IÈIÈIÈH È	H:ÈH5È0IÈ5H:È:IÈ=IÉIÉIÉIÉIc              ƒ   óú   K  — |r|j                  d«      syt        |t        ¬«      s|S |j                  d«      sy|€ddlm}  |d«      }t        || ||¬«      ƒ d{  –—† }|r|j                  d«      sy|S 7 Œ­w)	a!  Return an OAuth2 credential whose access_token is good for the next request.

    Returns the credential unchanged while its token is valid for at least
    ``MCP_PER_USER_TOKEN_EXPIRY_BUFFER_SECONDS``. Only when the token is expired (or
    expiring within that buffer) and a refresh_token is stored does it mint a new one
    via ``refresh_user_oauth_token``. Returns None when there is no usable token
    (missing token, expired with no refresh_token, or a failed refresh).

    The refresh_token is only ever sent to the server's token_url inside
    ``refresh_user_oauth_token``; it is never exposed to the caller beyond the cred
    dict it already holds. ``prisma_client`` is fetched lazily and only when a refresh
    actually happens, so the valid-token path never requires a DB handle.
    re   N)rÂ  rf   r   ©Úget_prisma_client_or_throwz3Database not connected. Cannot refresh OAuth token.)rÏ   rô   rÒ  rÁ  )r‡   rÉ  r   Úlitellm.proxy.utilsró  rð  )rô   rÒ  rÁ  rÏ   ró  Ú	refresheds         r:   Úresolve_valid_user_oauth_tokenrö  £  s…   è ø€ ñ& �t—x‘x Ô/ØÜ& tÔ<dÕeØˆØ�8‰8�OÔ$ØØÐÝBá2Ð3hÓiˆÜ5Ø#ØØØô	÷ €Iñ ˜IŸM™M¨.Ô9ØØÐðús   ‚AA;ÁA9ÁA;Úprefetched_credsc              ƒ   ó~  K  — t        |dd«      }| r|sy	 ddlm}m} |€|j	                  | |«      ƒ d{  –—† }|�|S d}|�|j	                  |«      }n#ddlm}	  |	d«      }t        || |«      ƒ d{  –—† }|r|j	                  d«      syt        | |||¬«      ƒ d{  –—† }|€|j                  | |«      ƒ d{  –—†  y|d   }
|€= ||t        |j	                  d	«      «      «      }|j                  | ||
|«      ƒ d{  –—†  |
S 7 ŒÏ7 Œ”7 Œk7 ŒQ7 Œ# t        $ r"}t        j                  d
| ||«       Y d}~yd}~ww xY w­w)al  Resolve a user's valid OAuth2 access token for a server: Redis cache, else DB + refresh.

    The egress token-resolution core shared by v1's header builder and the v2 ``OAuthTokenStore``
    adapter. Redis fast-path (skipped when ``prefetched_creds`` is supplied), else a DB read through
    ``resolve_valid_user_oauth_token`` (which refreshes an expired token when a ``refresh_token`` is
    stored), re-warming the Redis cache with the per-server TTL. Returns ``None`` when there is no
    usable token; any error is swallowed to ``None`` so a transient failure reads as "not
    authorized" rather than raising.
    rp   Nr   )Ú_compute_per_user_token_ttlÚmcp_per_user_token_cacherò  zCDatabase not connected. Connect a database to use OAuth2 MCP tools.re   )rô   rÒ  rÁ  rÏ   rm   zAresolve_user_oauth_access_token: failed for user=%s server=%s: %s)r“   Ú9litellm.proxy._experimental.mcp_server.oauth2_token_cacherù  rú  r‡   rô  ró  rË  rö  rE  Ú_remaining_token_secondsr*  rF  r   r”   )rô   rÒ  r÷  rp   rù  rú  Úcached_tokenrÏ   rÁ  ró  re   ÚttlrM  s                r:   Úresolve_user_oauth_access_tokenrÿ  Ë  s‚  è ø€ ô $+¨6°;ÀÓ#E€IÙ™)Øð0
÷	
ð
 Ð#Ø(@×(DÑ(DÀWÈiÓ(X×"XˆLØÐ'Ø#Ð#àˆØÐ'Ø#×'Ñ'¨	Ó2‰DåFá6ØUóˆMô 3°=À'È9ÓU×UˆDá˜4Ÿ8™8 NÔ3Øä3ØØØØ'ô	
÷ 
ˆð ˆ<ð +×1Ñ1°'¸9ÓE×EÐEØà#'¨Ñ#7ˆØÐ#Ù4°VÔ=UÐVZ×V^ÑV^Ð_kÓVlÓ=mÓnˆCØ*×.Ñ.¨w¸	À<ÐQTÓU×UÐUØÐðE #Yøð Vøð

øð Føð Vùäò 
Ü×$Ñ$ØOØØØ÷		
ð 	
ð ûð
üs¨   ‚D=–D µD¶D ½D=¾4D Á2DÁ3D Â
D=ÂD ÂD	ÂD Â9DÂ:D Â>D=Â??D Ã>DÃ?D ÄD=ÄD ÄD Ä	D ÄD ÄD Ä	D:ÄD5Ä0D=Ä5D:Ä:D=rm   c                 óB  — | sy	 t        j                  | «      }|j                  € |j                  t        j                  ¬«      }t        |t        j                  t        j                  «      z
  j                  «       «      }|dkD  r|S dS # t        t        f$ r Y yw xY w)zNSeconds until ``expires_at`` (ISO 8601), or None when absent/past/unparseable.NrÄ  r   )r   rÆ  rî   r�   rÅ  rÇ  r
   rf  rD   re  Útotal_seconds)rm   rÈ  Ú	remainings      r:   rü  rü    sŒ   € áØðÜ×'Ñ'¨
Ó3ˆð ‡}�}ÐØ—‘¤x§|¡|�Ó4ˆÜ˜F¤X§\¡\´(·,±,Ó%?Ñ?×NÑNÓPÓQ€IØ! Ašˆ9Ð/¨4Ð/øô œ	Ð"ò Ùðús   …B ÂBÂBc              ƒ   óž   K  — |sg S t        | |t        j                  dœ«      ƒ d{  –—† }|D �cg c]  }|j                  ‘Œ c}S 7 Œc c}w ­w)zGReturn active BYOM servers submitted by this user (creator visibility).)Úsubmitted_byr	  N)rå   r   Úactiverp   )rÏ   rô   r¨  r˜   s       r:   Ú,get_active_submitted_mcp_server_ids_for_userr    sT   è ø€ ñ
 Øˆ	ä0Øà#Ü0×7Ñ7ñ	
ó÷ €Dð &*Ö*˜cˆC�M‹MÒ*Ð*ðúò +ùs    ‚%A§A¨A°AÁAÁAc              ƒ   ó  K  — t        j                  t        j                  «      }t	        | |t
        j                  ||dœ«      ƒ d{  –—† }t        j                  |j                  «       «      }t        |j                  «       |S 7 Œ>­w)z2Set approval_status=active and record reviewed_at.©r	  Úreviewed_atrT  N)r   re  r
   rf  rï   r   r  r   r  r¼   r—   r…   )rÏ   rp   rQ  re  r‹  rÑ   s         r:   Úapprove_mcp_serverr
  0  st   è ø€ ô —‘œhŸl™lÓ+€CÜ4ØØä0×7Ñ7ØØ$ñ	
ó÷ €Gô *×8Ñ8¸×9KÑ9KÓ9MÓN€EÜ! %§.¡.Ô1Ø€Lðús   ‚AB	ÁBÁ	?B	Úreview_notesc              ƒ   ó(  K  — t        j                  t        j                  «      }t        j
                  ||dœ}|�||d<   t        | ||«      ƒ d{  –—† }t        j                  |j                  «       «      }t        |j                  «       |S 7 Œ>­w)zBSet approval_status=rejected, record reviewed_at and review_notes.r  Nr  )r   re  r
   rf  r   Úrejectedrï   r   r  r¼   r—   r…   )rÏ   rp   rQ  r  re  r©   r‹  rÑ   s           r:   Úreject_mcp_serverr  E  s„   è ø€ ô —‘œhŸl™lÓ+€Cä,×5Ñ5ØØ ñF€Dð
 ÐØ+ˆˆ^ÑÜ4°]ÀIÈtÓT×T€GÜ)×8Ñ8¸×9KÑ9KÓ9MÓN€EÜ! %§.¡.Ô1Ø€Lð Uús   ‚ABÁBÁ?Bc              ƒ   ó¤  K  — t        | «      j                  dddiiddid¬«      ƒ d{  –—† }|D �cg c]%  }t        j                  |j	                  «       «      ‘Œ' }}|D ]  }t        |j                  «       Œ t        d„ |D «       «      }t        d„ |D «       «      }t        d	„ |D «       «      }t        t        |«      ||||¬
«      S 7 ŒŸc c}w ­w)zæ
    Returns all MCP servers that were submitted by non-admin users (submitted_at IS NOT NULL),
    along with a summary count breakdown by approval_status.
    Mirrors get_guardrail_submissions() from guardrail_endpoints.py.
    Úsubmitted_atr  NÚdesciô  )rß   ÚorderÚtakec              3   ó\   K  — | ]$  }|j                   t        j                  k(  sŒ!d –— Œ& y­w©rb  N)r	  r   Úpending_review©Ú.0Úis     r:   ú	<genexpr>z&get_mcp_submissions.<locals>.<genexpr>m  s$   è ø€ Òc˜q¨Q×->Ñ->ÔBS×BbÑBbÓ-bœÑcùó   ‚",¥,c              3   ó\   K  — | ]$  }|j                   t        j                  k(  sŒ!d –— Œ& y­wr  )r	  r   r  r  s     r:   r  z&get_mcp_submissions.<locals>.<genexpr>n  s$   è ø€ ÒZ˜a¨A×,=Ñ,=ÔAR×AYÑAYÓ,YœÑZùr  c              3   ó\   K  — | ]$  }|j                   t        j                  k(  sŒ!d –— Œ& y­wr  )r	  r   r  r  s     r:   r  z&get_mcp_submissions.<locals>.<genexpr>o  s$   è ø€ Ò^ ¨a×.?Ñ.?ÔCT×C]ÑC]Ó.]œ!Ñ^ùr  )rr   r  r  r  r   )
rÓ   rã   r   r  r¼   r—   r…   Úsumr   r-  )rÏ   r¨  Úrr   ÚitemÚpendingr  r  s           r:   Úget_mcp_submissionsr"  Z  sæ   è ø€ ô LeØóLç�iØ  t˜}Ð-Ø˜vÐ&Øð ó ÷F€Dð TXÖXÈaÔ*×9Ñ9¸!¿,¹,».ÕIÐX€EÐXØò 5ˆÜ% d§m¡mÕ4ð5ô Ñc EÔcÓc€GÜÑZ 5ÔZÓZ€FÜÑ^ UÔ^Ó^€Hä Ü�%‹jØØØØôð ðFúò Yùs!   ‚'C©C	ªC²*CÁA.CÃCc                 óL  — t        | ddd¬«      }|€| rt        j                  d«       i S 	 t        j                  |«      }t        |t        «      si S |j                  «       D ��ci c]  \  }}t        |«      t        |«      “Œ c}}S # t
        t        f$ r i cY S w xY wc c}}w )zLDecrypt a ``values_b64`` blob and parse it as a flat ``{name: value}`` dict.r®  rŒ   Fr�   zªMCP per-user env vars failed to decrypt (LITELLM_SALT_KEY changed?); treating as unset so the user is prompted to re-enter them rather than silently forwarding ciphertext)r    r   r”   rš   r›   rî   r�   r]   r’   r   rB   )r“  r–   r¢  Úkr¥   s        r:   Ú_decode_user_env_varsr%  }  s¨   € ä+ØØØØ#ô	€Ið ÐÙÜ ×(Ñ(ðKôð
 ˆ	ðÜ—‘˜IÓ&ˆô �fœdÔ#Øˆ	Ø'-§|¡|£~×6™t˜q !ŒC�‹F”C˜“F‰NÓ6Ð6øô	 œ	Ð"ò ØŠ	ðüó 7s   ¬B	 Á&B Â	BÂBc              ƒ   ó�   K  — t        | «      j                  d||dœi¬«      ƒ d{  –—† }|€i S t        |j                  «      S 7 Œ­w)zIReturn the calling user's env var dict for ``server_id`` (empty if none).rö   r÷   râ   N)ró   rè   r%  r¯  rµ  s       r:   Úget_user_env_varsr'  —  sU   è ø€ ô -¨]Ó;×GÑGØ"°ÀiÑ$PÐQð Hó ÷ €Cð €{Øˆ	Ü  §¡Ó0Ð0ðús   ‚$A¦A§Ac              ƒ   óÄ   K  — t        |«      }|si S t        | |d|idœ«      ƒ d{  –—† }|D �ci c]"  }|j                  t        |j                  «      “Œ$ c}S 7 Œ2c c}w ­w)z“Return ``{server_id: {var_name: value}}`` for one user across many servers.

    Servers with no stored row are simply absent from the result.
    r  r÷   N)rq   r  rp   r%  r¯  )rÏ   rô   r  Úidsr¨  r˜   s         r:   Úget_user_env_vars_bulkr*  ¥  sb   è ø€ ô �jÓ!€CÙØˆ	Ü2°=ÈgÐeiÐknÐdoÑBpÓq×q€DØLPÖQÀSˆC�M‰MÔ0°·±Ó@Ñ@ÒQÐQð rúÚQùs    ‚$A ¦A§A ¯'AÁA ÁA ÚupdatesÚallowed_namesc              ƒ   ó  K  — t        |«      }t        j                  t        j                  |› d|› �j                  «       d¬«      j                  «       dd¬«      }t        | «      4 ƒd{  –—† }|j                  d|«      ƒ d{  –—†  |j                  j                  d	||d
œi¬«      ƒ d{  –—† }|�t        |j                  «      ni }	i |	¥|¥j                  «       D �
�ci c]  \  }
}|
|v sŒ|
|“Œ }}
}t        t        j                   |«      «      }|j                  j#                  d	||d
œi|||dœd|idœ¬«      ƒ d{  –—†  ddd«      ƒd{  –—†  |S 7 Œï7 Œ×7 Œ°c c}}
w 7 Œ"7 Œ# 1 ƒd{  –—†7  sw Y   S xY w­w)a˜  Merge ``updates`` into the user's stored env vars for ``server_id`` and
    return the resulting set.

    The read-modify-write runs inside a transaction guarded by a
    ``(user_id, server_id)`` advisory lock so two concurrent writes from the
    same user can't drop one update. Names outside ``allowed_names`` are pruned,
    so an admin retiring a user-scoped variable also clears its stored value.
    ú:é   )Údigest_sizeÚbigT)ÚsignedNz(SELECT pg_advisory_xact_lock($1::bigint)rö   r÷   râ   )rô   rp   r¯  r¯  rþ   rì   )r*  rD   Ú
from_bytesÚhashlibÚblake2bÚencodeÚdigestrÞ   r;   r2   rè   r%  r¯  r   r!   rš   r¾  r   )rÏ   rô   rp   r+  r,  ÚallowedÚlock_keyrÜ   r˜   r]  r$  r¥   rz   r²  s                 r:   Úmerge_user_env_varsr:  µ  s¬  è ø€ ô ˜Ó'€GÜ—n‘nÜ�‰˜7˜) 1 Y KÐ0×7Ñ7Ó9ÀqÔI×PÑPÓRØØð %ó €Hô
 ' }Ó5÷ 
ð 
¸Ø�n‰nÐGÈÓR×RÐRØKM×KdÑKd×KpÑKpØ&°GÈ)Ñ(TÐUð Lqó L
÷ F
ˆð DGÀ?Ô/°·±Ô?ÐXZˆØ*A¨XÐ*A¸Ð*A×*GÑ*GÓ*I×Z¡$ ! QÈQÐRYÊ\˜˜A™ÐZˆÑZÜ-¬d¯j©j¸Ó.@ÓAˆØ×'Ñ'×.Ñ.Ø&°GÈ)Ñ(TÐUð  'Ø!*Ø")ñð
 (¨Ð1ñð /ó 

÷ 
	
ð 
	
÷
÷ 
ð& €Mð'
øØRøðF
úó [ð
	
øð
ø÷ 
÷ 
ð 
ð& €Müs‘   ‚A)FÁ+EÁ,FÁ/E+ÂEÂ(E+Â.EÂ/4E+Ã#E!Ã0E!Ã5AE+ÅE'ÅE+Å	FÅE)ÅFÅE+ÅE+Å!E+Å)FÅ+E>Å1E4Å2E>Å9Fc              ƒ   ó\   K  — t        | «      j                  ||dœ¬«      ƒ d{  –—†  y7 Œ­w)zÍRemove the calling user's env var values for ``server_id``.

    Uses ``delete_many`` so a missing row is a no-op; real DB errors still
    propagate to the caller instead of being silently swallowed.
    r÷   râ   N)ró   rG  rø   s      r:   Údelete_user_env_varsr<  à  s*   è ø€ ô   Ó
.×
:Ñ
:ÈWÐclÑAmÐ
:Ó
n×nÒnús   ‚",¤*¥,)FNr7   )NNNF)r   )¤r–  r™  r4  rš   Úcollections.abcr   r   r   r   r   r   r	   r
   Útypingr   r   r   r   r   r   Úlitellm._loggingr   Úlitellm._uuidr   Úlitellm.constantsr   Ú&litellm.llms.custom_httpx.http_handlerr   Ú2litellm.proxy._experimental.mcp_server.oauth_utilsr   Úlitellm.proxy._typesr   r   r   r   r   r   r   r   r   Ú0litellm.proxy.common_utils.encrypt_decrypt_utilsr   r    r!   rô  r"   Ú1litellm.repositories.object_permission_repositoryr#   Ú%litellm.repositories.prisma_protocolsr$   Ú'litellm.repositories.table_repositoriesr%   r&   r'   Ú$litellm.repositories.team_repositoryr(   Ú2litellm.repositories.verification_token_repositoryr)   Úlitellm.types.llms.custom_httpr*   Úlitellm.types.mcpr+   rs  r,   Úprisma_db_modelsr-   Úprisma_db_typesÚ+litellm.types.mcp_server.mcp_server_managerr.   r0   rG   Ú	frozensetrZ   rA   rB   r_   r`   rc   rg   ri   rk   rt   ry   r’   rC   r�   rO   r„   r‰   r—   r    rq   r¨   r*  rÄ   r¾   rÎ   rÓ   rÕ   r×   rÙ   rÞ   rå   rê   rï   rñ   ró   rù   rû   r  r  r  r  r  r  r#  r(  r1  r8  r;  r=  r?  rP  rW  rD   r`  rZ  rn  r  r�  r„  rˆ  r’  rœ  rž  r£  r¥  r¬  r°  r³  r¶  r¸  rº  rÀ  rÉ  rË  rÎ  rÑ  ÚtuplerÖ  rÚ  rð  rö  rÿ  rü  r  r
  r  r"  r%  r'  r*  r:  r<  r8   rE   r:   ú<module>rR     sA  ðÜ Û Û Û ß LÕ Lß 2Ñ 2ß G× Gå 1Ý Ý FÝ IÝ b÷
÷ 
õ 
÷ñ õ
 -Ý XÝ >÷ñ õ
 @õõ @Ý ,áÝ1Ý/åEôG Hô Gôj˜hô jñ 5>òó5Ð ˜%Ð 0Ñ1ó ð !˜# ™*ð !¨¨t©ó !ñ :Còó:Ð ˜uÐ%5Ñ6ó ñ 9BÐCUÐWgÐBhÓ8iÐ ˜eÐ$4Ñ5Ó iñ <EÒEtÓ;uÐ  Ð'7Ñ!8Ó uô )ô ôÐ8Àõ ô °%õ ð c¨D¡jð °S¸4±Zó ð°$°s¸F°{Ñ2Cð ÐPTÐUXÐZ`ÐU`ÑPað ÐfjÐknÐpvÐkvÑfwó ð< Fð <¨tó <ð9¨X°d¸3À¸8±nÑ-Eð 9È$ó 9ð $$¨H°YÀÀcÈ3ÀhÁÑ5OÑ,PÐSWÑ,Wð $$Ð\`ó $$ðN,¨6ð ,°dó ,ð:-(Ø�H˜W S¨# XÑ.Ñ/Ñ/°$Ñ6ð-(ØLOð-(à	ˆ$ˆs�Cˆx‰.Ñ˜DÑ ó-(ðd  Ø"&ñhØ
Ð 6Ñ
6ðhàðhð �C‘˜4‘ðhð 
ˆ#ˆsˆ(�^ó	hðV- ^ð -ÀSÈ4ÁZð -ÐTbó -ð`¨C°'¸#¸v¸+Ñ2FÑ,Fð È4ÐPSÐU[ÐP[ÑK\ó ð
Øðà<óðØðà?óðØðà7óðØðàBóð¨<ð Ð<Só ð HLñQØðQàDðQð 9óQðfØðfØ,/ðfà5ófðØðàðð >ðð /ó	ðØðà@óðØðà<óðØðØ*-ðØ:=ðà9óð LPñPØðPàHðPð =óPðØðØ*-ðØ:=ðØORðà	óð$ HLñMØðMàDðMð 9óMðØðàóð6 #'ñØðà˜4‘Zðð 
Ð
 Ñ!óð4	¨ð 	Àð 	ÐI_ÐbfÑIfó 	ð¨ð À8ÈCÁ=ð ÐUYÐZpÑUqó ð(¸lð ÐSVð Ð[_Ð`cÑ[dó ð*°ð Èð ÐPTÐUXÑPYó ð&Øðà
ðð 
Ð
 Ñ!óð:%Øð%Ø03ð%à?ó%ð*ØðØ,/ðà;óð&°\ð Ècó òð LPñAØðAàðAð % c¨3 Z°¸4±Ð%@ÑAÀDÑHðAð ˜dÑ"ó	AðHØðØ':ðØHKðàóð: !ñ'IØð'Ià
ð'Ið ð'Ið ð	'Ið
 �T‰zð'Ið ó'IðTB¸,ð BÐUXð BÐ]aó Bð,ØðØ,/ðØ>Aðà˜dÑ"óð< #'ñ	zØðzà
 ðzð ðzð �C‘˜4‘ð	zð
 ˜dÑ"ózðzÀð ÐZ]ð ÐbhÐkoÑboó ðØðØ,/ðØ>Lðà	óð*!Ø4ð!ØFIð!àˆ4�Zó!ð"-À,ð -Ð\_ð -Ðqtó -ð` Cð ¨C°$©Jó ð,¨Cð ¸Cð ÀDó ð #¨¡*ð Ð1GÈ$Ñ1Nó ð0A #ð AÐ*@À4Ñ*Gó Að
%Àð %Ð^aó %ðP)¸\ð )Ð[^ó )ðX	UØð	Uàð	Uð ð	Uð ð		Uð
 
ó	Uð
7Øð
7àð
7ð ð
7ð 	ˆ4�Zó	
7ðØðàðð ðð 
ó	ðØðàðð ðð 
ó	ð& !%Ø!Ø#Ø!ñ<UØð<Uàð<Uð ð<Uð ð	<Uð
 ˜‘:ð<Uð �d‘
ð<Uð �‰I˜Ñð<Uð ð<Uð 
ó<Uñ~Ð&<ð Ècð ÐZ^ó ð*)Øð)àð)ð ð)ð ˜dÑ"ó	)ð Øðàðð 
Ð
 Ñ!óð( t¨C°¨KÑ'8ð Àð Èó ð" Vð "°°f¸c°kÑ0Bó "ðP LPñ-Øð-àð-ð % c¨3 Z°¸4±Ð%@ÑAÀDÑHð-ð 	ó	-ð`iNØðiNàðiNð ðiNð !ð	iNð
 ˜dÑ"óiNð` *.ñ	%Øð%àð%ð ! 4Ñ
'ð%ð   $Ñ&ð	%ð
 ˜dÑ"ó%ðV EIñBØ�4‰ZðBàðBð ˜cÐ#9Ð9Ñ:¸TÑAðBð 	ˆ4�Zó	BðJ0¨¨t©ð 0¸¸d¹
ó 0ð+Øð+àð+ð 
ˆ#�Yó+ð$Øðàðð ðð ó	ð2  $ñ	Øðàðð ðð ˜‘*ð	ð
 óð*ØðàóðF7 #ð 7¨$¨s°C¨x©.ó 7ð41Øð1àð1ð ð1ð 
ˆ#ˆsˆ(�^ó	1ðRØðRàðRð ˜‘ðRð 
ˆ#ˆt�C˜�H‰~Ð
Ñó	Rð (Øð(àð(ð ð(ð �#�s�(‰^ð	(ð
 ˜C‘=ð(ð 
ˆ#ˆsˆ(�^ó(ðV
oØð
oàð
oð ð
oð 
ô	
orE   