§
    ÚŽ’j <  ã                   óØ   — d Z ddlZddlmZmZ ddlmZmZ ddlZddl	Z
ddl
mZmZmZ ddlm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mZ er	ddlmZmZ neZeZ G d„ de¦  «        ZdS )z1Unified MCP client implementation for AgentScope.é    N)ÚAsyncExitStackÚ_AsyncGeneratorContextManager)ÚAnyÚTYPE_CHECKING)ÚClientSessionÚstdio_clientÚStdioServerParameters)Ú
sse_client)Ústreamable_http_client)ÚFieldÚ	BaseModelÚPrivateAttré   )ÚStdioMCPConfigÚHttpMCPConfigé   )Úlogger)ÚMCPToolÚToolBasec                   ó˜  — e Zd ZU dZ edd¬¦  «        Zeed<    edd¬¦  «        Ze	ed<    ed	d
d¬¦  «        Z
eez  ed<   dZee         dz  ed<   	 dZee         dz  ed<   	 dZedz  ed<   	  ed¬¦  «        Zeed<    ed¬¦  «        Zedz  ed<    ed¬¦  «        Zedz  ed<    ed¬¦  «        Ze	ed<    ed¬¦  «        Zeej        j                 dz  ed<   ede	fd„¦   «         Zdeddfd„Z d(d„Z!de"e         fd„Z#d(d„Z$d)d!e	ddfd"„Z%de"e         fd#„Z&deej        j                 fd$„Z'dee(         fd%„Z)dede*fd&„Z+d(d'„Z,dS )*Ú	MCPClientaG  The unified MCP client in AgentScope.

    This class provides a unified interface for MCP connections, handling both
    stateful (persistent) and stateless (ephemeral) connections.

    - Stateful: Requires explicit connect() and close(), maintains session
    - Stateless: No connect() needed, creates temporary session per call

    Private attributes:
    - _client: The underlying MCP client context manager
    - _session: The MCP ClientSession (for stateful connections only)
    - _stack: AsyncExitStack for managing connection lifecycle
    - _is_connected: Connection state flag
    - _cached_tools: Cached list of tools

    Example:

    .. code-block:: python

        # Stateful connection (STDIO or HTTP)
        client = MCPClient(
            name="file_system",
            is_stateful=True,
            mcp_config=StdioMCPConfig(
                command="mcp-server-filesystem"
            )
        )
        await client.connect()
        tools = await client.list_tools()
        await client.close()

        # Stateless connection (HTTP only)
        client = MCPClient(
            name="weather_search",
            is_stateful=False,
            mcp_config=HttpMCPConfig(
                url="https://api.weather.com/mcp"
            )
        )
        # No connect() needed
        tools = await client.list_tools()

    zMCP NamezThe MCP name.)ÚtitleÚdescriptionÚnameÚStatefulz�Whether this is a stateful connection that requires explicit connect() and close(). STDIO MCP must be stateful. HTTP MCP can be either stateful or stateless.Úis_statefulÚtypez
MCP ConfigzThe MCP server configuration.)Údiscriminatorr   r   Ú
mcp_configNÚenable_toolsÚdisable_toolsÚexecution_timeout)ÚdefaultÚ_clientÚ_sessionÚ_stackFÚ_is_connectedÚ_cached_toolsÚreturnc                 ó   — | j         S )zuWhether the client is currently connected.

        Returns:
            True if connected, False otherwise.
        )r'   ©Úselfs    úHC:\Projects\DSCons\.venv\Lib\site-packages\agentscope\mcp\_mcp_client.pyÚis_connectedzMCPClient.is_connectedl   s   € ð Ô!Ð!ó    Ú_MCPClient__contextc                 ó  — t          j        d| j        ¦  «        st          d| j        › d�¦  «        ‚| j        j        dk    r| j        st          d¦  «        ‚| j        �Pt          | j        t          ¦  «        rt          d„ | j        D ¦   «         ¦  «        rt          d| j        › d	�¦  «        ‚| j        �Pt          | j        t          ¦  «        rt          d
„ | j        D ¦   «         ¦  «        rt          d| j        › d	�¦  «        ‚| j        �f| j        �_t          | j        ¦  «                             t          | j        ¦  «        ¦  «        }t          |¦  «        dk    rt          d|› d	�¦  «        ‚|                      ¦   «          dS )z-Validate configuration and initialize client.z[a-zA-Z0-9_-]+zMCPClient name 'zh' contains characters not allowed by LLM providers (only [a-zA-Z0-9_-] are permitted). Please rename it.Ú	stdio_mcpz.STDIO MCP must be stateful (is_stateful=True).Nc              3   óB   K  — | ]}t          |t          ¦  «         V — Œd S ©N©Ú
isinstanceÚstr©Ú.0Ú_s     r-   ú	<genexpr>z,MCPClient.model_post_init.<locals>.<genexpr>ˆ   s@   è è € ð >ð >Ø+,•J˜q¥#Ñ&Ô&Ð&ð>ð >ð >ð >ð >ð >r/   z2Enable tools should be a list of strings, but got ú.c              3   óB   K  — | ]}t          |t          ¦  «         V — Œd S r4   r5   r8   s     r-   r;   z,MCPClient.model_post_init.<locals>.<genexpr>‘   s@   è è € ð ?ð ?Ø+,•J˜q¥#Ñ&Ô&Ð&ð?ð ?ð ?ð ?ð ?ð ?r/   z3Disable tools should be a list of strings, but got r   zHThe tools in enable_tools and disable_tools should not overlap, but got )ÚreÚ	fullmatchr   Ú
ValueErrorr   r   r   r    r6   ÚlistÚanyr!   ÚsetÚintersectionÚlenÚ_initialize_client)r,   r0   rD   s      r-   Úmodel_post_initzMCPClient.model_post_initu   s  € õ Œ|Ð-¨t¬yÑ9Ô9ð 	Ýð1 4¤9ð 1ð 1ð 1ñô ð ð Œ?Ô ;Ò.Ð.°tÔ7GÐ.ÝØ@ñô ð ð
 ÔÐ(Ý˜dÔ/µÑ6Ô6ð ½#ð >ð >Ø04Ô0Að>ñ >ô >ñ ;ô ;ð õ !ð,ØÔ(ð,ð ,ð ,ñô ð ð
 ÔÐ)Ý˜dÔ0µ$Ñ7Ô7ð ½3ð ?ð ?Ø04Ô0Bð?ñ ?ô ?ñ <ô <ð õ !ð-ØÔ)ð-ð -ð -ñô ð ð
 ÔÐ(¨TÔ-?Ð-KÝ˜tÔ0Ñ1Ô1×>Ò>Ý�DÔ&Ñ'Ô'ñô ˆLõ �<Ñ Ô  AÒ%Ð%Ý ðCØ3?ðCð Cð Cñô ð ð 	×ÒÑ!Ô!Ð!Ð!Ð!r/   c           
      óè   — | j         j        dk    ra| j         }t          t          |j        |j        pg |j        |j        rt          |j        ¦  «        ndd|j	        ¬¦  «        ¦  «        | _
        dS dS )uÍ  Pre-build the stdio client context manager.

        Only the stdio transport is materialised at construction time â€”
        ``StdioServerParameters`` carry process-launch details that are
        cheap to bind upfront. HTTP transports are built lazily inside
        :meth:`connect` (or per-call inside :meth:`_get_client_gen` for
        stateless mode), since each ``streamable_http_client`` /
        ``sse_client`` is a one-shot context manager.
        r2   Nzutf-8)ÚcommandÚargsÚenvÚcwdÚencodingÚencoding_error_handler)r   r   r   r	   rI   rJ   rK   rL   r7   rN   r$   )r,   Úconfigs     r-   rF   zMCPClient._initialize_client¦   s}   € ð Œ?Ô ;Ò.Ð.Ø”_ˆFÝ'Ý%Ø"œNØœÐ*¨Øœ
Ø+1¬:Ð?�˜FœJ™œ˜¸4Ø$Ø+1Ô+Hðñ ô ñ	ô 	ˆDŒLˆLˆLð /Ð.r/   c                 óF  — | j         }|j                             d¦  «        s|j                             d¦  «        r!t          |j        |j        |j        ¬¦  «        S d}|j        s|j        r t          j        |j        |j        ¬¦  «        }t          |j        |¬¦  «        S )z3Create an HTTP MCP client (SSE or streamable HTTP).z/ssez
/messages/)ÚurlÚheadersÚtimeoutN)rR   rS   )rQ   Úhttp_client)	r   rQ   Úendswithr
   rR   rS   ÚhttpxÚAsyncClientr   )r,   rO   rT   s      r-   Ú_create_http_clientzMCPClient._create_http_client½   s¾   € ð ”ˆð Œ:×Ò˜vÑ&Ô&ð 	¨&¬*×*=Ò*=¸lÑ*KÔ*Kð 	ÝØ”JØœØœðñ ô ð ð ˆØŒ>ð 	˜Vœ^ð 	ÝÔ+ØœØœðñ ô ˆKõ &Ø”
Ø#ð
ñ 
ô 
ð 	
r/   c              ƒ   óÞ  K  — | j         st          j        d| j        ¦  «         dS | j        rt          d| j        › d�¦  «        ‚| j        €)| j        j        dk    r|  	                    ¦   «         | _        t          ¦   «         | _        	 | j                             | j        ¦  «        ƒ d{V —†}|d         |d         }}t          ||¦  «        | _        | j                             | j        ¦  «        ƒ d{V —† | j                             ¦   «         ƒ d{V —† d| _        t          j        d	| j        ¦  «         dS # t"          $ r( | j                             ¦   «         ƒ d{V —† d| _        ‚ w xY w)
zÂConnect to the MCP server (for stateful connections only).

        For stateless connections, this method does nothing.

        Raises:
            RuntimeError: If already connected.
        z5Stateless MCP '%s' does not require explicit connect.NúMCP 'z9' is already connected. Call close() before reconnecting.Úhttp_mcpr   r   TzMCP connected: %s)r   r   Údebugr   r'   ÚRuntimeErrorr$   r   r   rX   r   r&   Úenter_async_contextr   r%   Ú
initializeÚinfoÚ	ExceptionÚaclose)r,   ÚcontextÚread_streamÚwrite_streams       r-   ÚconnectzMCPClient.connect×   s²  è è € ð Ôð 	ÝŒLØGØ”	ñô ð ð ˆFàÔð 	Ýð4˜œ	ð 4ð 4ð 4ñô ð ð Œ<Ð D¤OÔ$8¸JÒ$FÐ$FØ×3Ò3Ñ5Ô5ˆDŒLå$Ñ&Ô&ˆŒð	Ø œK×;Ò;¸D¼LÑIÔIÐIÐIÐIÐIÐIÐIˆGØ(/°¬
°G¸A´J˜ˆKÝ)¨+°|ÑDÔDˆDŒMØ”+×1Ò1°$´-Ñ@Ô@Ð@Ð@Ð@Ð@Ð@Ð@Ð@Ø”-×*Ò*Ñ,Ô,Ð,Ð,Ð,Ð,Ð,Ð,Ð,à!%ˆDÔÝŒKÐ+¨T¬YÑ7Ô7Ð7Ð7Ð7øÝð 	ð 	ð 	Ø”+×$Ò$Ñ&Ô&Ð&Ð&Ð&Ð&Ð&Ð&Ð&ØˆDŒKØð	øøøs   Â	B/D: Ä:2E,TÚignore_errorsc              ƒ   ó  K  — | j         st          j        d| j        ¦  «         dS | j        st          d| j        › d�¦  «        ‚	 | j                             ¦   «         ƒ d{V —† nC# t          $ r6}|s|‚t          j	        d| j        t          |¦  «        ¦  «         Y d}~nd}~ww xY wd| _        d| _        d| _        t          j        d| j        ¦  «         dS # d| _        d| _        d| _        t          j        d| j        ¦  «         w xY w)a  Close the MCP connection (for stateful connections only).

        For stateless connections, this method does nothing.

        Args:
            ignore_errors: Whether to ignore errors during cleanup.

        Raises:
            RuntimeError: If not connected.
        z3Stateless MCP '%s' does not require explicit close.NrZ   ú)' is not connected. Call connect() first.zError closing MCP '%s': %sFzMCP closed: %s)r   r   r\   r   r'   r]   r&   rb   ra   Úwarningr7   r%   r`   )r,   rg   Úes      r-   ÚclosezMCPClient.close   sj  è è € ð Ôð 	ÝŒLØEØ”	ñô ð ð ˆFàÔ!ð 	Ýð(˜œ	ð (ð (ð (ñô ð ð
	5Ø”+×$Ò$Ñ&Ô&Ð&Ð&Ð&Ð&Ð&Ð&Ð&Ð&øÝð 	ð 	ð 	Ø ð Ø�ÝŒNØ,Ø”	Ý�A‘”ñô ð ð ð ð ð ð øøøøð	øøøð ˆDŒKØ ˆDŒMØ!&ˆDÔÝŒKÐ(¨$¬)Ñ4Ô4Ð4Ð4Ð4øð ˆDŒKØ ˆDŒMØ!&ˆDÔÝŒKÐ(¨$¬)Ñ4Ô4Ð4Ð4øøøs0   ÁA& Á%C Á&
B&Á0,B!ÂC Â!B&Â&C Ã1Dc                 óX   — | j         j        dk    r| j        S |                      ¦   «         S )z/Get client generator for stateless connections.r2   )r   r   r$   rX   r+   s    r-   Ú_get_client_genzMCPClient._get_client_gen(  s,   € àŒ?Ô ;Ò.Ð.Ø”<Ðà×+Ò+Ñ-Ô-Ð-r/   c              ƒ   óŽ  ‡ K  — ‰ j         sÊ‰                      ¦   «         4 ƒd{V —†}|d         |d         }}t          ||¦  «        4 ƒd{V —†	 }|                     ¦   «         ƒ d{V —† |                     ¦   «         ƒ d{V —†}|j        ‰ _        ddd¦  «        ƒd{V —† n# 1 ƒd{V —†swxY w Y   ddd¦  «        ƒd{V —† n# 1 ƒd{V —†swxY w Y   n?‰                      ¦   «          ‰ j                             ¦   «         ƒ d{V —†}|j        ‰ _        ‰ j        }‰ j	        �ˆ fd„|D ¦   «         }‰ j
        �ˆ fd„|D ¦   «         }|S )aÿ  List available tools from the MCP server in raw
        :class:`mcp.types.Tool` form, applying ``enable_tools`` and
        ``disable_tools`` filtering.

        The full (unfiltered) tool list is cached on ``_cached_tools`` so
        :meth:`get_tool` can resolve names that were filtered out as well.

        Returns:
            `list[mcp.types.Tool]`:
                Raw MCP tool descriptors after filtering.

        Raises:
            RuntimeError: If not connected (for stateful connections).
        Nr   r   c                 ó0   •— g | ]}|j         ‰j        v ¯|‘ŒS © )r   r    )r9   Útoolr,   s     €r-   ú
<listcomp>z,MCPClient.list_raw_tools.<locals>.<listcomp>Q  s3   ø€ ð ð ð àØ”9 Ô 1Ð1Ð1ð à1Ð1Ð1r/   c                 ó0   •— g | ]}|j         ‰j        v¯|‘ŒS rq   )r   r!   ©r9   r:   r,   s     €r-   rs   z,MCPClient.list_raw_tools.<locals>.<listcomp>W  s/   ø€ ð ð ð Ø¨a¬f¸DÔ<NÐ.NÐ.N�Ð.NÐ.NÐ.Nr/   )r   rn   r   r_   Ú
list_toolsÚtoolsr(   Ú_validate_connectionr%   r    r!   )r,   Úclird   re   ÚsessionÚresÚavailable_toolss   `      r-   Úlist_raw_toolszMCPClient.list_raw_tools/  s¶  øè è € ð Ôð 	+à×+Ò+Ñ-Ô-ð 3ð 3ð 3ð 3ð 3ð 3ð 3°Ø,/°¬F°C¸´F˜\�Ý(ØØ ñô ð 3ð 3ð 3ð 3ð 3ð 3ð 3ð 3ð Ø!×,Ò,Ñ.Ô.Ð.Ð.Ð.Ð.Ð.Ð.Ð.Ø '× 2Ò 2Ñ 4Ô 4Ð4Ð4Ð4Ð4Ð4Ð4�CØ),¬�DÔ&ð3ð 3ð 3ñ 3ô 3ð 3ð 3ð 3ð 3ð 3ð 3ð 3ð 3ð 3ð 3ð 3ð 3ð 3ð 3ð 3ð 3ð 3ð 3øøøð 3ð 3ð 3ð 3ð3ð 3ð 3ñ 3ô 3ð 3ð 3ð 3ð 3ð 3ð 3ð 3ð 3ð 3ð 3ð 3ð 3ð 3ð 3ð 3ð 3ð 3ð 3øøøð 3ð 3ð 3ð 3øð ×%Ò%Ñ'Ô'Ð'Øœ×0Ò0Ñ2Ô2Ð2Ð2Ð2Ð2Ð2Ð2ˆCØ!$¤ˆDÔà $Ô 2ˆØÔÐ(ðð ð ð à+ðñ ô ˆOð
 ÔÐ)ðð ð ð Ø*ðñ ô ˆOð Ðs6   ¥'CÁAB ÂCÂ 
B*	Â*CÂ-B*	Â.CÃ
CÃCc              ƒ   ód   ‡ K  — ‰                       ¦   «         ƒ d{V —†}ˆ fd„|D ¦   «         ƒ d{V —†S )a   List available tools from the MCP server as wrapped
        :class:`ToolBase` instances. If `enable_tools` and `disable_tools`
        are not `None` in the constructor, the returned tools will be
        filtered accordingly.

        Returns:
            `list[ToolBase]`:
                List of available MCP tools.

        Raises:
            RuntimeError: If not connected (for stateful connections).
        Nc              “   óT   •K  — g | ]"}‰                      |j        ¦  «        ƒ d {V —†‘Œ#S r4   )Úget_toolr   ru   s     €r-   rs   z(MCPClient.list_tools.<locals>.<listcomp>j  s=   øè è € Ð?Ð?Ð?°�d—m’m A¤FÑ+Ô+Ð+Ð+Ð+Ð+Ð+Ð+Ð?Ð?Ð?r/   )r}   )r,   Ú	raw_toolss   ` r-   rv   zMCPClient.list_tools\  sX   øè è € ð ×-Ò-Ñ/Ô/Ð/Ð/Ð/Ð/Ð/Ð/ˆ	Ø?Ð?Ð?Ð?°YÐ?Ñ?Ô?Ð?Ð?Ð?Ð?Ð?Ð?Ð?r/   c              ƒ   ór  K  — ddl m} | j        €|                      ¦   «         ƒ d{V —† d}| j        D ]}|j        |k    r|} nŒ|€t          d|› d| j        › d�¦  «        ‚| j        s || j        || j        | j        ¬¦  «        S |  	                    ¦   «           || j        || j
        | j        ¬¦  «        S )	a  Get a tool by name from the MCP server.

        The returned MCPTool object implements ToolProtocol and can be:
        - Called directly: `await tool(arg1=val1)`
        - Registered to toolkit: `toolkit.register_tool(tool)`

        Args:
            name: The name of the tool function to get.

        Returns:
            A tool object that implements ToolProtocol.

        Raises:
            ValueError: If the tool is not found.
            RuntimeError: If not connected (for stateful connections).
        r   )r   NzTool 'z' not found in MCP server 'ú')Úmcp_namerr   Ú
client_genrS   )r„   rr   rz   rS   )rr   r   r(   r}   r   r@   r   rn   r"   rx   r%   )r,   r   r   Útarget_toolrr   s        r-   r€   zMCPClient.get_tooll  s*  è è € ð* 	#Ð"Ð"Ð"Ð"Ð"ð ÔÐ%Ø×%Ò%Ñ'Ô'Ð'Ð'Ð'Ð'Ð'Ð'Ð'ð ˆØÔ&ð 	ð 	ˆDØŒy˜DÒ Ð Ø"�Ø�ð !ð ÐÝØJ˜ÐJÐJ¸d¼iÐJÐJÐJñô ð ð
 Ôð 	à�7ØœØ ØÔ/ØÔ.ð	ñ ô ð ð ×%Ò%Ñ'Ô'Ð'Ø�7ØœØ ØœØÔ.ð	ñ ô ð r/   c                 ó‚   — | j         st          d| j        › d�¦  «        ‚| j        st          d| j        › d�¦  «        ‚dS )z”Validate connection state for stateful connections.

        Raises:
            RuntimeError: If not connected or session not initialized.
        rZ   ri   z3' session is not initialized. Call connect() first.N)r'   r]   r   r%   r+   s    r-   rx   zMCPClient._validate_connection§  sy   € ð Ô!ð 	Ýð(˜œ	ð (ð (ð (ñô ð ð Œ}ð 	Ýð(˜œ	ð (ð (ð (ñô ð ð	ð 	r/   )r)   N)T)-Ú__name__Ú
__module__Ú__qualname__Ú__doc__r   r   r7   Ú__annotations__r   Úboolr   r   r   r    rA   r!   r"   Úfloatr   r$   r   r%   r   r&   r   r'   r(   ÚmcpÚtypesÚToolÚpropertyr.   rG   rF   r   rX   rf   rl   rn   r}   r   rv   r   r€   rx   rq   r/   r-   r   r      s  € € € € € € ð*ð *ðX �ØØ#ðñ ô €Dˆ#ð ð ñ ð
 ˜Øð3ðñ ô €K�ð ð ñ ð 27°ØØØ3ð2ñ 2ô 2€J� Ñ.ð ð ñ ð &*€L�$�s”)˜dÑ"Ð)Ð)Ñ)ðð '+€M�4˜”9˜tÑ#Ð*Ð*Ñ*ðð '+Ð�u˜t‘|Ð*Ð*Ñ*ØOð �; tÐ,Ñ,Ô,€GˆSÐ,Ð,Ñ,Ø%0 [¸Ð%>Ñ%>Ô%>€Hˆm˜dÑ"Ð>Ð>Ñ>Ø$/ K¸Ð$=Ñ$=Ô$=€FˆN˜TÑ!Ð=Ð=Ñ=Ø%˜+¨eÐ4Ñ4Ô4€M�4Ð4Ð4Ñ4Ø1<°ÀTÐ1JÑ1JÔ1J€M�4˜œ	œÔ'¨$Ñ.ÐJÐJÑJàð"˜dð "ð "ð "ñ „Xð"ð/"¨ð /"°ð /"ð /"ð /"ð /"ðbð ð ð ð.
à	& sÔ	+ð
ð 
ð 
ð 
ð4'ð 'ð 'ð 'ðR&5ð &5¨ð &5¸ð &5ð &5ð &5ð &5ðP.Ð!>¸sÔ!Cð .ð .ð .ð .ð+ d¨3¬9¬>Ô&:ð +ð +ð +ð +ðZ@ $ x¤.ð @ð @ð @ð @ð 9àð9ð 
ð9ð 9ð 9ð 9ðvð ð ð ð ð r/   r   )r‹   r>   Ú
contextlibr   r   Útypingr   r   rV   Ú	mcp.typesr�   r   r   r	   Úmcp.client.sser
   Úmcp.client.streamable_httpr   Úpydanticr   r   r   Ú_configr   r   Ú_loggingr   rr   r   r   r   rq   r/   r-   ú<module>r›      sN  ðà 7Ð 7Ø 	€	€	€	Ø DÐ DÐ DÐ DÐ DÐ DÐ DÐ DØ %Ð %Ð %Ð %Ð %Ð %Ð %Ð %à €€€Ø Ð Ð Ð Ø BÐ BÐ BÐ BÐ BÐ BÐ BÐ BÐ BÐ BØ %Ð %Ð %Ð %Ð %Ð %Ø =Ð =Ð =Ð =Ð =Ð =Ø 2Ð 2Ð 2Ð 2Ð 2Ð 2Ð 2Ð 2Ð 2Ð 2à 2Ð 2Ð 2Ð 2Ð 2Ð 2Ð 2Ð 2Ø Ð Ð Ð Ð Ð àð Ø(Ð(Ð(Ð(Ð(Ð(Ð(Ð(Ð(à€GØ€Hð^ð ^ð ^ð ^ð ^�	ñ ^ô ^ð ^ð ^ð ^r/   