+
    i(F                    <  a  R^ tj0 t R t^ RIt^ RIt^ RIt^ RIt^ RIt^ RIt^ RIt^ RI	t	^ RI
t
^ RIt^ RIHtHtHtHt ]P"                  ! ]4      tRtRtRtRtRt ^ RIHtHt ^ RIHt Rt ^ RIHt Rt ^ RIH!t! Rt" ^ R	I#H$t$H%t%H&t&H't'H(t(H)t)H*t* Rt ^ RI#H,t,H-t-H.t.H/t/ RtR R lt0]0! 4       t]'       d   ]'       g   ]PW                  R4       ^xt1^<t2^t3^<t4]5! 0 R_m4      t6]Pn                  ! R]Pp                  4      t9R R lt:R R lt;R R lt<R R lt=R R lt>]?^3R lt@ ! R R4      tA ! R R 4      tB/ tC] ^ k RsD] ^k RsE] ^k ]
P                  ! 4       tG]H! 4       tI] ^k R! R" ltJR# tKR$ tLR`R% R& lltMR' tNR( R) ltOR* R+ ltPR, R- ltQR. R/ ltRR0 R1 ltSR2 R3 ltTR4 R5 ltUR6 R7 ltVR8 R9 ltWR: R; ltXR< R= ltYRaR> R? lltZR@ RA lt[RB RC lt\RbRD RE llt]RFRFRGRGRHRHRIRI/t^RJ RK lt_RL RM lt`RN RO ltaRP RQ ltbRR RS ltcRT RU ltdRV RW lteRX RY ltfRZ tgR[ R\ lthR] tiR#   ]  d    Rt ELi ; i  ]  d    Rt" ELi ; i  ]  d    ]PW                  R
4        ELi ; i  ]  d    ]PW                  R4        ELi ; i  ]  d    ]PW                  R4        ELi ; i)ca  
MCP (Model Context Protocol) Client Support

Connects to external MCP servers via stdio or HTTP/StreamableHTTP transport,
discovers their tools, and registers them into the hermes-agent tool registry
so the agent can call them like any built-in tool.

Configuration is read from ~/.hermes/config.yaml under the ``mcp_servers`` key.
The ``mcp`` Python package is optional -- if not installed, this module is a
no-op and logs a debug message.

Example config::

    mcp_servers:
      filesystem:
        command: "npx"
        args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
        env: {}
        timeout: 120         # per-tool-call timeout in seconds (default: 120)
        connect_timeout: 60  # initial connection timeout (default: 60)
      github:
        command: "npx"
        args: ["-y", "@modelcontextprotocol/server-github"]
        env:
          GITHUB_PERSONAL_ACCESS_TOKEN: "ghp_..."
      remote_api:
        url: "https://my-mcp-server.example.com/mcp"
        headers:
          Authorization: "Bearer sk-..."
        timeout: 180
      analysis:
        command: "npx"
        args: ["-y", "analysis-server"]
        sampling:                    # server-initiated LLM requests
          enabled: true              # default: true
          model: "gemini-3-flash"    # override model (optional)
          max_tokens_cap: 4096       # max tokens per request
          timeout: 30                # LLM call timeout (seconds)
          max_rpm: 10                # max requests per minute
          allowed_models: []         # model whitelist (empty = all)
          max_tool_rounds: 5         # tool loop limit (0 = disable)
          log_level: "info"          # audit verbosity

Features:
    - Stdio transport (command + args) and HTTP/StreamableHTTP transport (url)
    - Automatic reconnection with exponential backoff (up to 5 retries)
    - Environment variable filtering for stdio subprocesses (security)
    - Credential stripping in error messages returned to the LLM
    - Configurable per-server timeouts for tool calls and connections
    - Thread-safe architecture with dedicated background event loop
    - Sampling support: MCP servers can request LLM completions via
      sampling/createMessage (text and tool-use responses)

Architecture:
    A dedicated background event loop (_mcp_loop) runs in a daemon thread.
    Each MCP server runs as a long-lived asyncio Task on this loop, keeping
    its transport context alive. Tool call coroutines are scheduled onto the
    loop via ``run_coroutine_threadsafe()``.

    On shutdown, each server Task is signalled to exit its ``async with``
    block, ensuring the anyio cancel-scope cleanup happens in the *same*
    Task that opened the connection (required by anyio).

Thread safety:
    _servers and _mcp_loop/_mcp_thread are accessed from both the MCP
    background thread and caller threads.  All mutations are protected by
    _lock so the code is safe regardless of GIL presence (e.g. Python 3.13+
    free-threading).
N)AnyDictListOptionalF)ClientSessionStdioServerParameters)stdio_clientT)streamablehttp_client)streamable_http_client)CreateMessageResultCreateMessageResultWithTools	ErrorDataSamplingCapabilitySamplingToolsCapabilityTextContentToolUseContentz5MCP sampling types not available -- sampling disabled)ServerNotificationToolListChangedNotificationPromptListChangedNotificationResourceListChangedNotificationzGMCP notification types not available -- dynamic tool discovery disabledz6mcp package not installed -- MCP tool support disabledc                $    V ^8  d   QhR\         /#    returnbool)formats   "+/home/ubuntu/hermes-agent/tools/mcp_tool.py__annotate__r      s           c                     \         '       g   R#  R\        P                  ! \        4      P                  9   #   \
        \        3 d     R# i ; i)zCheck if ClientSession accepts ``message_handler`` kwarg.

Inspects the constructor signature for backward compatibility with older
MCP SDK versions that don't support notification handlers.
Fmessage_handler)_MCP_AVAILABLEinspect	signaturer   
parameters	TypeError
ValueError r   r   _check_message_handler_supportr)      sD     > G$5$5m$D$O$OOOz" s   &7 AAzKMCP SDK does not support message_handler -- dynamic tool discovery disabledz(?:ghp_[A-Za-z0-9_]{1,255}|sk-[A-Za-z0-9_]{1,255}|Bearer\s+\S+|token=[^\s&,;\"']{1,255}|key=[^\s&,;\"']{1,255}|API_KEY=[^\s&,;\"']{1,255}|password=[^\s&,;\"']{1,255}|secret=[^\s&,;\"']{1,255})c                F    V ^8  d   QhR\         \        ,          R\        /# )r   user_envr   )r   dict)r   s   "r   r   r      s      htn  r   c                    / p\         P                  P                  4        F-  w  r#V\        9   g   VP	                  R4      '       g   K)  W1V&   K/  	  V '       d   VP                  V 4       V# )al  Build a filtered environment dict for stdio subprocesses.

Only passes through safe baseline variables (PATH, HOME, etc.) and XDG_*
variables from the current process environment, plus any variables
explicitly specified by the user in the server config.

This prevents accidentally leaking secrets like API keys, tokens, or
credentials to MCP server subprocesses.
XDG_)osenvironitems_SAFE_ENV_KEYS
startswithupdate)r+   envkeyvalues   &   r   _build_safe_envr8      sU     Cjj&&(
. CNN6$:$:H ) 

8Jr   c                0    V ^8  d   QhR\         R\         /# )r   textr   str)r   s   "r   r   r      s     7 7# 7# 7r   c                .    \         P                  RV 4      # )zStrip credential-like patterns from error text before returning to LLM.

Replaces tokens, keys, and other secrets with [REDACTED] to prevent
accidental credential exposure in tool error responses.
z
[REDACTED])_CREDENTIAL_PATTERNsub)r:   s   &r   _sanitize_errorr@      s     ""<66r   c                <    V ^8  d   QhR\         R\        R\         /# )r   r5   	directoryr   r,   r<   )r   s   "r   r   r      s!      t   r   c                N   \        T ;'       g    / 4      pV'       g   V# VP                  RR4      pVP                  \        P                  4       Uu. uF  qD'       g   K  VNK  	  ppW9  d   V.VOpV'       d    \        P                  P                  V4      MTVR&   V# u upi )z=Prepend *directory* to env PATH if it is not already present.PATH )r,   getsplitr/   pathsepjoin)r5   rB   updatedexistingpartpartss   &&    r   _prepend_pathrO      s    399"oG{{62&H&nnRZZ8A8dDTT8EA#U#05bjjooe,9GFON	 Bs   	B"!B"c                ^    V ^8  d   QhR\         R\        R\        \         \        3,          /# )r   commandr5   r   )r<   r,   tuple)r   s   "r   r   r      s*     !* !*C !*d !*uS$Y7G !*r   c           
        \         P                  P                  \        V 4      P	                  4       4      p\        T;'       g    / 4      p\         P                  V9  Edm   RV9   d
   VR,          MRp\        P                  ! W$R7      pV'       d   TpEM7VR
9   Ed0   \         P                  P                  \         P                  ! R\         P                  P                  \         P                  P                  R4      R4      4      4      p\         P                  P                  VRRV4      \         P                  P                  \         P                  P                  R4      R	RV4      .pV FY  p\         P                  P                  V4      '       g   K*  \         P                  ! V\         P                  4      '       g   KW  Tp M	  \         P                  P                  V4      p	V	'       d   \        W94      pW#3# )zResolve a stdio MCP command against the exact subprocess environment.

This primarily exists to make bare ``npx``/``npm``/``node`` commands work
reliably even when MCP subprocesses run under a filtered PATH.
rE   N)pathnodeHERMES_HOME~z.hermesbinz.local>   npmnpxrU   )r/   rT   
expanduserr<   stripr,   sepshutilwhichgetenvrJ   isfileaccessX_OKdirnamerO   )
rQ   r5   resolved_commandresolved_envpath_arg	which_hithermes_home
candidates	candidatecommand_dirs
   &&        r   _resolve_stdio_commandrm      sf    ww))#g,*<*<*>?		r?L	vv%%+1\+A<'tLL!1A	(!77'',,		!277<<0B0B30G#SK [&%9IJRWW//4hGWXJ (	77>>),,9bgg1N1N'0$ (
 ''//"23K$\?))r   c                0    V ^8  d   QhR\         R\        /# )r   excr   )BaseExceptionr<   )r   s   "r   r   r     s     93 93} 93 93r   c                \  aa R V3R lloR V3R lloS! V 4      pV'       d?   RV R2p\         P                  P                  V4      R
9   d
   VR,          p\        V4      # . pS! V 4       F  pWC9  g   K  VP	                  V4       K  	  \        RP                  VR	,          4      4      # )zERender nested MCP connection errors into an actionable short message.c                F    V ^8  d   QhR\         R\        \        ,          /# r   currentr   )rp   r   r<   )r   s   "r   r   +_format_connect_error.<locals>.__annotate__  s      } # r   c                   < \        V R R4      pV'       d    V F  pS! V4      pV'       g   K  Vu # 	  R# \        V \        4      '       db   \        V RR4      '       d   \        V P                  4      # \
        P                  ! R\        V 4      4      pV'       d   VP                  ^4      # R F;  p\        WR4      p\        V\        4      '       g   K'  S! V4      pV'       g   K9  Vu # 	  R# )
exceptionsNfilenamez$No such file or directory: '([^']+)'	__cause____context__)	getattr
isinstanceFileNotFoundErrorr<   rx   researchgrouprp   )rt   nestedchildmissingmatchattr
nested_exc_find_missings   &      r   r   ,_format_connect_error.<locals>._find_missing  s    ,5'.7"N   g011w
D117++,,IIEs7|TE{{1~%0D 5J*m44'
37"N 1 r   c                F    V ^8  d   QhR\         R\        \        ,          /# rs   )rp   r   r<   )r   s   "r   r   ru   '  s     8 8= 8T#Y 8r   c                   < \        V R R4      pV'       d%   . pV F  pVP                  S! V4      4       K  	  V# . p\        V 4      P                  4       pV'       d   VP	                  V4       R F>  p\        WR4      p\        V\        4      '       g   K'  VP                  S! V4      4       K@  	  T;'       g    V P                  P                  .# )rw   Nry   )	r|   extendr<   r\   appendr}   rp   	__class____name__)	rt   r   	flattenedr   messagesr:   r   r   _flatten_messagess	   &       r   r   0_format_connect_error.<locals>._flatten_messages'  s    ,5#%I  !25!9:  7|!!#OOD!0D 5J*m44 1* => 1 77G--6677r   zmissing executable ''z (ensure Node.js is installed and PATH includes its bin directory, or set mcp_servers.<name>.command to an absolute path and include that directory in mcp_servers.<name>.env.PATH)z; :N   N>   rY   rZ   rU   )r/   rT   basenamer@   r   rJ   )ro   r   messagededupeditemr   r   s   &    @@r   _format_connect_errorr     s     ,8 8" C G(	377G$(>>AG
 w''G!#&NN4  ' 499WR[122r   c                     V! V 4      p\        V\        4      '       d   \        P                  ! V4      '       g   V# \	        WC4      #   \
        \        \        3 d    Tu # i ; i)zCoerce a config value to a numeric type, returning *default* on failure.

Handles string values from YAML (e.g. ``"10"`` instead of ``10``),
non-finite floats, and values below *minimum*.
)r}   floatmathisfinitemaxr&   r'   OverflowError)r7   defaultcoerceminimumresults   &&&& r   _safe_numericr   N  sV    fe$$T]]6-B-BN6##z=1 s   9A	 A	 
A	 	A$#A$c                      a  ] tR tRt o RtRRRRRR/tV 3R	 lR
 ltV 3R lR ltV 3R lR lt]	V 3R lR l4       t
V 3R lR lt]	RV 3R lR ll4       tR tR tV 3R lR ltR tRtV tR# )SamplingHandleri]  a  Handles sampling/createMessage requests for a single MCP server.

Each MCPServerTask that has sampling enabled creates one SamplingHandler.
The handler is callable and passed directly to ``ClientSession`` as
the ``sampling_callback``.  All state (rate-limit timestamps, metrics,
tool-loop counters) lives on the instance -- no module-level globals.

The callback is async and runs on the MCP background event loop.  The
sync LLM call is offloaded to a thread via ``asyncio.to_thread()`` so
it doesn't block the event loop.
stopendTurnlength	maxTokens
tool_callstoolUsec                &   < V ^8  d   QhRS[ RS[/# )r   server_nameconfigr<   r,   )r   __classdict__s   "r   r   SamplingHandler.__annotate__l  s     [ [C [ [r   c                   Wn         \        VP                  R ^
4      ^
\        4      V n        \        VP                  R^4      ^\
        4      V n        \        VP                  RR4      R\        4      V n        \        VP                  R^4      ^\        ^ R7      V n        VP                  R4      V n	        VP                  R. 4      V n
        R\        P                  R	\        P                  R
\        P                  /pVP                  \        VP                  RR	4      4      P!                  4       \        P                  4      V n        . V n        ^ V n        R^ R^ R^ R^ /V n        R# )max_rpmtimeoutmax_tokens_capi   max_tool_rounds)r   modelallowed_modelsdebuginfowarning	log_levelrequestserrorstokens_usedtool_use_countN)r   r   rG   intr   r   r   r   r   model_overrider   loggingDEBUGINFOWARNINGr<   loweraudit_level_rate_timestamps_tool_loop_countmetrics)selfr   r   _log_levelss   &&& r   __init__SamplingHandler.__init__l  s&   &$VZZ	2%>CH$VZZ	2%>EJ+FJJ7G,NPTVYZ,JJ(!,aa 
 %jj1$jj)92>vw||YPWP_P_`&??

;/0668',,

 .0 !"AxM1FVXYZr   c                    < V ^8  d   QhRS[ /# r   r   )r   r   s   "r   r   r     s      4 r   c                0   \         P                   ! 4       pV^<,
          pV P                   Uu. uF  q3V8  g   K  VNK  	  upV P                  R&   \        V P                  4      V P                  8  d   R# V P                  P	                  V4       R# u upi )zASliding-window rate limiter.  Returns True if request is allowed.:NNNFT)timer   lenr   r   )r   nowwindowts   &   r   _check_rate_limit!SamplingHandler._check_rate_limit  sz    iikr/3/D/D#S/D!F
AA/D#Sa t$$%5$$S)	 $Ts
   BBc                0   < V ^8  d   QhRS[ S[,          /# r   )r   r<   )r   r   s   "r   r   r     s      Xc] r   c                (   V P                   '       d   V P                   # V'       dl   \        VR4      '       dZ   VP                  '       dH   VP                   F7  p\        VR4      '       g   K  VP                  '       g   K+  VP                  u # 	  R# )z3Config override > server hint > None (use default).hintsnameN)r   hasattrr   r   )r   preferenceshints   && r   _resolve_modelSamplingHandler._resolve_model  sh    &&&7;88[=N=N=N#))4((TYYY99$ * r   c                    < V ^8  d   QhRS[ /# r   r;   )r   r   s   "r   r   r     s     O OC Or   c                    \        V R4      '       d   V P                  f   R# \        V P                  \        4      '       d   V P                  MV P                  .pRP	                  R V 4       4      # )z,Extract text from a ToolResultContent block.contentrF   
c              3   b   "   T F%  p\        VR 4      '       g   K  VP                  x  K'  	  R# 5i)r:   N)r   r:   ).0r   s   & r   	<genexpr><SamplingHandler._extract_tool_result_text.<locals>.<genexpr>  s     Nutf8Mus   //)r   r   r}   listrJ   )blockr1   s   & r   _extract_tool_result_text)SamplingHandler._extract_tool_result_text  sS     ui((EMM,A!+EMM4!@!@u}}oyyNuNNNr   c                0   < V ^8  d   QhRS[ S[,          /# r   r   r,   )r   r   s   "r   r   r     s     C C4: Cr   c                   . pVP                    EF  p\        VR4      '       d   VP                  M8\        VP                  \
        4      '       d   VP                  MVP                  .pV Uu. uF  p\        VR4      '       g   K  VNK  	  ppV Uu. uFA  p\        VR4      '       g   K  \        VR4      '       g   K+  \        VR4      '       d   K?  VNKC  	  ppV Uu. uF?  p\        VR4      '       d   K  \        VR4      '       d   \        VR4      '       d   K=  VNKA  	  ppV F3  p	VP                  RRRV	P                  RV P                  V	4      /4       K5  	  V'       Ed
   . p
V F  pT
P                  R	\        VR	R
\        V
4       24      RRRRVP                  R\        VP                  \        4      '       d!   \        P                  ! VP                  4      M\!        VP                  4      //4       K  	  RVP"                  RV
/pV Uu. uF#  p\        VR4      '       g   K  VP$                  NK%  	  ppV'       d   RP'                  V4      VR&   VP                  V4       EKc  V'       g   EKn  \        V4      ^8X  dM   \        V^ ,          R4      '       d4   VP                  RVP"                  RV^ ,          P$                  /4       EK  . pV F  p\        VR4      '       d"   VP                  RRRVP$                  /4       K6  \        VR4      '       dF   \        VR4      '       d4   VP                  RRRRRVP(                   RVP*                   2//4       K  \,        P/                  R\1        V4      P2                  4       K  	  V'       g   EK  VP                  RVP"                  RV/4       EK  	  V# u upi u upi u upi u upi )a$  Convert MCP SamplingMessages to OpenAI format.

Uses ``msg.content_as_list`` (SDK helper) so single-block and
list-of-blocks are handled uniformly.  Dispatches per block type
with ``isinstance`` on real SDK types when available, falling back
to duck-typing via ``hasattr`` for compatibility.
content_as_list	toolUseIdr   inputroletooltool_call_idr   idcall_typefunction	argumentsr   r:   r   datamimeType	image_urlurlzdata:z;base64,z5Unsupported sampling content block type: %s (skipped))r   r   r   r}   r   r   r   r   r   r|   r   r   r   r,   jsondumpsr<   r   r:   rJ   r   r   loggerr   r   r   )r   paramsr   msgblocksbtool_results	tool_usescontent_blockstrtc_listtumsg_dict
text_partsrN   r   s   &&              r   _convert_messages!SamplingHandler._convert_messages  s>     "??C,3C9J,K,KS(()#++t<<3;;- 
 (.Iv!K1HAAvLI$*yFqga.@WQPWEXahijlwaxFIy)/  EAwq+7NaX_`aciXjXjovwx  {B  pCaaN  E #F"BLLt==bA!  # y#BNNgb$%G~0FG
""BGG'BHHVZA[A[BHH)=adegememan%$  $ #)#((L'!J.<Sn6@Rfaffn
S*.))J*?HY')~&!+q8I60R0ROOVSXXy.QRBSBXBX$YZE!/"5&11!LL&&&%**)MN$UF33z8R8R!LL & +euU^^<LHUZU_U_T`5a-b* 
 #NN W $U 4 4 "0 u 9e(LMq #t i Jy E0 TsB   .OOO,O OO O9!OOO0Oc                &   < V ^8  d   QhRS[ RS[/# )r   r   code)r<   r   )r   r   s   "r   r   r     s     ! ! !3 !r   c                H    \         '       d   \        WR7      # \        V 4      h)z1Return ErrorData (MCP spec) or raise as fallback.)r  r   )_MCP_SAMPLING_TYPESr   	Exception)r   r  s   &&r   _errorSamplingHandler._error  s      $88  r   c                p   V P                   R;;,          ^,          uu&   V P                  ^ 8X  d'   ^ V n        V P                  RV P                   R24      # V ;P                  ^,          un        V P                  V P                  8  d4   ^ V n        V P                  RV P                   RV P                   R24      # . pVP
                  P                   F  pVP                  P                  p\        V\        4      '       d    \        P                  ! V4      pM%\        V\"        4      '       d   TMR\        V4      /pVP%                  \'        R	VP(                  VP                  P*                  VR
7      4       K  	  \        P-                  V P.                  RV P                  VP0                  \3        \3        VRR4      RR4      \5        V4      4       \7        RVVP0                  RR7      #   \        P                  \        3 d)    \        P!                  RT P                  T4       RT/p Li ; i)zEBuild a CreateMessageResultWithTools from an LLM tool_calls response.r   z Tool loops disabled for server 'z' (max_tool_rounds=0)z%Tool loop limit exceeded for server 'z' (max z rounds)zRMCP server '%s': malformed tool_calls arguments from LLM (wrapping as raw): %.100s_rawtool_use)r   r   r   r   zEMCP server '%s' sampling response: model=%s, tokens=%s, tool_calls=%dusageNtotal_tokens?	assistantr   r   r   r   
stopReason)r   r   r   r  r   r   r   r   r   r}   r<   r  loadsJSONDecodeErrorr'   r  r   r,   r   r   r   r   logr   r   r|   r   r   )r   choiceresponser  tcargsparseds   &&&    r   _build_tool_use_result&SamplingHandler._build_tool_use_result  s   %&!+& 1$$%D!;;243C3C2DDYZ  	"  4#7#77$%D!;;78H8H7I J,,-X7 
 ..++B;;((D$$$,!ZZ-F ",D$!7!7fc$i=P!!.55[[%%	#  ,, 	

ShnnGHgt4ncJ	
 ,".. 	
 	
3 ,,j9 ,NN=(($
 %d^F,s   G22A H54H5c                   ^ V n         VP                  P                  ;'       g    Rp\        P	                  V P
                  RV P                  VP                  \        \        VRR4      RR4      4       \        R\        R\        V4      R	7      VP                  V P                  P                  VP                  R
4      R7      # )z8Build a CreateMessageResult from a normal text response.rF   z6MCP server '%s' sampling response: model=%s, tokens=%sr  Nr  r  r   r:   )r   r:   r   r!  )r   r   r   r  r%  r   r   r   r|   r   r   r@   _STOP_REASON_MAPrG   finish_reason)r   r&  r'  response_texts   &&& r   _build_text_result"SamplingHandler._build_text_result,  s     !..44"

DhnnGHgt4ncJ		
 #V/-2PQ..,,001E1EyQ	
 	
r   c                    < V ^8  d   QhRS[ /# r   r,   )r   r   s   "r   r   r   A  s     
 
 
r   c                2    RV R\        \        4       R7      /# )z<Return kwargs to pass to ClientSession for sampling support.sampling_callbacksampling_capabilities)tools)r   r   r   s   &r   session_kwargsSamplingHandler.session_kwargsA  s$      #%7-/&
 	
r   c                
  a aaaaaa"   S P                  4       '       gw   \        P                  RS P                  S P                  4       S P
                  R;;,          ^,          uu&   S P                  RS P                   RS P                   R24      # S P                  \        VRR4      4      p^ RI	H
o T;'       g    S P                  ;'       g    R	oS P                  '       d   S'       d   SS P                  9  d~   \        P                  R
S P                  S4       S P
                  R;;,          ^,          uu&   S P                  RS RS P                   RRP                  S P                  4       24      # S P                  V4      o\        VR4      '       d3   VP                   '       d!   SP#                  ^ RRRVP                   /4       \%        VP&                  S P(                  4      oRo\        VR4      '       d   VP*                  e   VP*                  oRo\        VRR4      pV'       dQ   V Uu. uFC  pRRRR\        VRR	4      R\        VRR	4      ;'       g    R	R\-        \        VRR4      4      //NKE  	  upo\        P/                  S P0                  RS P                  SS\3        S4      4       VVVVVVV 3R lp \4        P6                  ! \4        P8                  ! V4      S P:                  R7      G Rj  xL
 p\        TR"R4      '       g?   S P
                  R;;,          ^,          uu&   S P                  R#S P                   R 24      # TPD                  ^ ,          p	S P
                  R$;;,          ^,          uu&   \        \        TR%R4      R&^ 4      p
\G        T
\H        4      '       d    S P
                  R';;,          T
,          uu&   T	PJ                  R(8X  dJ   \        T	PL                  R(4      '       d.   T	PL                  PN                  '       d   S PQ                  Y4      # S PS                  Y4      # u upi  ELE  \4        P<                   dO    S P
                  R;;,          ^,          uu&   S P                  RS P:                   RS P                   R 24      u # \>         dP   pS P
                  R;;,          ^,          uu&   S P                  R!\A        \C        T4      4       24      u Rp?# Rp?ii ; i5i))zSampling callback invoked by the MCP SDK.

Conforms to ``SamplingFnT`` protocol.  Returns
``CreateMessageResult``, ``CreateMessageResultWithTools``, or
``ErrorData``.
z5MCP server '%s' sampling rate limit exceeded (%d/min)r   z)Sampling rate limit exceeded for server 'z' (z requests/minute)modelPreferencesN)call_llmrF   z:MCP server '%s' requested model '%s' not in allowed_modelszModel 'z' not allowed for server 'z'. Allowed: , systemPromptr   systemr   temperaturer8  r   r   r   descriptionr%   inputSchemazFMCP server '%s' sampling request: model=%s, max_tokens=%d, messages=%dc            
      J   < S ! R S;'       g    RSSSSSP                   R7      # )mcpN)taskr   r   rB  
max_tokensr8  r   r   )r>  call_temperature
call_toolsrH  r   resolved_modelr   s   r   
_sync_call,SamplingHandler.__call__.<locals>._sync_call  s1    $,,!,%  r   rI  z"Sampling LLM call timed out after zs for server 'r   zSampling LLM call failed: choicesz5LLM returned empty response (no choices) for server 'r   r  r  r   r   )*r   r  r   r   r   r   r  r   r|   agent.auxiliary_clientr>  r   r   rJ   r  r   r@  insertminr   r   rB  _normalize_mcp_input_schemar%  r   r   asynciowait_for	to_threadr   TimeoutErrorr  r@   r<   rO  r}   r   r/  r   r   r+  r1  )r   contextr  r   server_toolsr   rM  r'  ro   r&  r  r>  rJ  rK  rH  r   rL  s   f&&        @@@@@@r   __call__SamplingHandler.__call__L  st     %%''NNG  $,, LL"a'";;;D<L<L;M NLL>!24  ##GF4F$MN 	4 ;;$"5"5;;>nDL_L_6_NNL  . LL"a'";;.) *$$%\$))D<O<O2P1QS  ))&16>**v/B/B/BOOA)V=P=PQR ))4+>+>?
6=))f.@.@.L%11 
vw5 & &A J62 6%wq-'D'J'J$&A#A}d;'!	 &J 	

Tnj#h-	
		 			$--!!*-t|| H  xD11LL"a'";;$$%Q(  !!!$Z A% wx$?QRSlC((LL'<7'   L055)))..v@@&&v88OB ## 	LL"a'";;4T\\N C#//03   	LL"a'";;,_SX-F,GH 	s   B7TTT*T2CT5B	T>)Q(QAT9Q QQ DT%'TQ A T
/T1T
:T
;AT?T
 TT

T)r   r   r   r   r   r   r   r   r   r   r   N))r   
__module____qualname____firstlineno____doc__r.  r   r   r   staticmethodr   r  r  r+  r1  r:  rZ  __static_attributes____classdictcell__r   s   @r   r   r   ]  s     
 	8[,PYZ[ [.   O OC CN ! ! !7
r
*
 
}9 }9r   r   c                      a  ] tR tRt o RtRtV 3R lR ltV 3R lR ltR tR t	V 3R	 lR
 lt
V 3R lR ltR tV 3R lR ltV 3R lR ltR tRtV tR# )MCPServerTaski  aO  Manages a single MCP server connection in a dedicated asyncio Task.

The entire connection lifecycle (connect, discover, serve, disconnect)
runs inside one asyncio Task so that anyio cancel-scopes created by
the transport client are entered and exited in the same Task context.

Supports both stdio and HTTP/StreamableHTTP transports.
c                    < V ^8  d   QhRS[ /# )r   r   r;   )r   r   s   "r   r   MCPServerTask.__annotate__  s     , ,S ,r   c                4   Wn         R V n        \        V n        R V n        \
        P                  ! 4       V n        \
        P                  ! 4       V n        . V n	        R V n
        / V n        R V n        . V n        RV n        \
        P                  ! 4       V n        R # )NrF   )r   session_DEFAULT_TOOL_TIMEOUTtool_timeout_taskrT  Event_ready_shutdown_event_toolsr  _config	_sampling_registered_tool_names
_auth_typeLock_refresh_lock)r   r   s   &&r   r   MCPServerTask.__init__  sq    	&*#8-1
mmo&}}+/4813#!$\\^r   c                    < V ^8  d   QhRS[ /# r   r   )r   r   s   "r   r   rh    s     % %$ %r   c                     RV P                   9   # )z)Check if this server uses HTTP transport.r  )rr  r9  s   &r   _is_httpMCPServerTask._is_http  s    $$r   c                   a  V 3R lpV# )zBuild a ``message_handler`` callback for ``ClientSession``.

Dispatches on notification type.  Only ``ToolListChangedNotification``
triggers a refresh; prompt and resource change notifications are
logged as stubs for future work.
c                   <"    \        V \        4      '       d$   \        P                  R SP                  V 4       R# \
        '       d   \        V \        4      '       d   V P                  ;\        Rc ;e>   w    \        P                  RSP                  4       SP                  4       G Rj  xL
  R#  ;\        Rc ;e&   w    \        P                  RSP                  4       R#  \        Rc ;e%   w   \        P                  RSP                  4       R#   R# R# R#  Lp  \         d%    \        P                  RSP                  4        R# i ; i5i)z'MCP message handler (%s): exception: %sNr(   z9MCP server '%s': received tools/list_changed notificationz/MCP server '%s': prompts/list_changed (ignored)z1MCP server '%s': resources/list_changed (ignored)z%Error in MCP message handler for '%s')r}   r  r  r   r   _MCP_NOTIFICATION_TYPESr   rootr   r   _refresh_toolsr   r   	exception)r   r   s   &r   _handler5MCPServerTask._make_message_handler.<locals>._handler  s    Ugy11LL!JDIIW^_**z'CU/V/V!,,:8:"KK [ $		 #'"5"5"777 ; =:<"LL)Z\`\e\ef =<>"LL)\^b^g^gh ?  0W* 8  U  !H$))TUso   E7D  ED  
A#D  -D.D  2E40D  $E&.D  ED  ED   +EEEEr(   )r   r  s   f r   _make_message_handler#MCPServerTask._make_message_handler  s    	U* r   c           	       "   ^ RI Hp ^ RIHp V P                  ;_uu_4       GRj  xL
  V P
                  P                  4       G Rj  xL
 p\        VR4      '       d   VP                  M. pVP                  4        FL  w  rVVP                  R4      '       g   K  VR,           Uu. uF  qwV P                  9  g   K  VNK  	  upVR&   KN  	  V P                   F  pVP                  V4       K  	  W@n        \        V P                  W P                   4      V n        \"        P%                  RV P                  \'        V P                  4      4       RRR4      GRj  xL
  R#  EL< ELu upi  L  + GRj  xL 
 '       g   i     R# ; i5i)u9  Re-fetch tools from the server and update the registry.

Called when the server sends ``notifications/tools/list_changed``.
The lock prevents overlapping refreshes from rapid-fire notifications.
After the initial ``await`` (list_tools), all mutations are synchronous
— atomic from the event loop's perspective.
registryTOOLSETSNr8  hermes-z1MCP server '%s': dynamically refreshed %d tool(s))tools.registryr  toolsetsr  rw  rj  
list_toolsr   r8  r1   r3   rt  
deregisterrq  _register_server_toolsr   rr  r  r   r   )	r   r  r  tools_resultnew_mcp_toolsts_nametsr   prefixed_names	   &        r   r  MCPServerTask._refresh_tools  s"     	,%%%%%!%!8!8!::L29,2P2PL..VXM  (~~/%%i00.0k"bkdFaFa=a11k"bBwK  0
 "&!<!<##M2 "= (K*@		4+D' KKC		3t::;) &%%: #c &%%%s|   %FE#FE0	E&
AE0E0'E)
>E)
BE0FE.F&E0)E0.F0F
	6E97
F
	F
		Fc                    < V ^8  d   QhRS[ /# r   r   r4  )r   r   s   "r   r   rh  7  s     18 18t 18r   c           
       "   VP                  R4      pVP                  R. 4      pVP                  R4      pV'       g   \        RV P                   R24      h\        V4      p\	        W%4      w  r%^ RIHp V! W#4      pV'       d   \        RV P                   RV 24      h\        TTV'       d   TMRR	7      pV P                  '       d   V P                  P                  4       M/ p	\        '       d    \        '       d   V P                  4       V	R
&   \        4       p
\        V4      ;_uu_4       GRj  xL
 w  r\        4       V
,
          pV'       d-   \        ;_uu_ 4        \         P#                  V4       RRR4       \%        W3/ V	B ;_uu_4       GRj  xL
 pVP'                  4       G Rj  xL
  Wn        V P+                  4       G Rj  xL
  V P,                  P/                  4        V P0                  P3                  4       G Rj  xL
  RRR4      GRj  xL
  RRR4      GRj  xL
  X'       d/   \        ;_uu_ 4        \         P5                  V4       RRR4       R# R#  EL3  + '       g   i     L; i L L L Lx Lj  + GRj  xL 
 '       g   i     L; i Lx  + GRj  xL 
 '       g   i     L; i  + '       g   i     R# ; i5i)z%Run the server using stdio transport.rQ   r)  r5   MCP server 'z' has no 'command' in config)check_package_for_malwarez': N)rQ   r)  r5   r!   )rG   r'   r   r8   rm   tools.osv_checkr  r   rs  r:  r  _MCP_MESSAGE_HANDLER_SUPPORTEDr  _snapshot_child_pidsr   _lock_stdio_pidsr4   r   
initializerj  _discover_toolsro  setrp  waitdifference_update)r   r   rQ   r)  r+   safe_envr  malware_errorserver_paramssampling_kwargspids_beforeread_streamwrite_streamnew_pidsrj  s   &&             r   
_run_stdioMCPServerTask._run_stdio7  s    **Y'zz&"%::e$tyyk)EF  #8,27E 	>1'@tyyk]O<  .$$
 >B^^^$..779QS""'E'E151K1K1MO-. +,...2M;+-;HU&&x0 $[R/RRRV]((***&**,,,!**//111 SR /. --h7   / UR*,1 SRRR /... s  CK'K3K?4K3I&4K7*J$!I)	7 J$I<J$J	/I>0J	J ;J		J
J	J$JJ$K)J"*K6KK K)I94	J$>J	 J	J	J$JJ
JJJ$"K$J=	*J-+
J=	6J=	8K K		Kc                    < V ^8  d   QhRS[ /# r  r4  )r   r   s   "r   r   rh  j  s     J6 J6d J6r   c                L  "   \         '       g   \        RV P                   R24      hVR,          p\        VP	                  R4      ;'       g    / 4      pVP	                  R\
        4      pRpV P                  R8X  d*    ^ RIHp V! V P                  W!P	                  R4      4      pV P                  '       d   V P                  P                  4       M/ p\        '       d    \        '       d   V P!                  4       VR
&   \"        '       Ed=   ^ RIp	RRRV	P'                  \)        V4      RR7      /p
V'       d   W:R&   Ve   WZR&   V	P*                  ! R/ V
B ;_uu_4       GRj  xL
 p\-        W+R7      ;_uu_4       GRj  xL
 w  rp\/        W3/ VB ;_uu_4       GRj  xL
 pVP1                  4       G Rj  xL
  Wn        V P5                  4       G Rj  xL
  V P6                  P9                  4        V P:                  P=                  4       G Rj  xL
  RRR4      GRj  xL
  RRR4      GRj  xL
  RRR4      GRj  xL
  R# RVR\)        V4      /pVe   VVR&   \?        V3/ VB ;_uu_4       GRj  xL
 w  rp\/        W3/ VB ;_uu_4       GRj  xL
 pVP1                  4       G Rj  xL
  Wn        V P5                  4       G Rj  xL
  V P6                  P9                  4        V P:                  P=                  4       G Rj  xL
  RRR4      GRj  xL
  RRR4      GRj  xL
  R#   \         d(   p\        P                  R	T P                  T4       h Rp?ii ; i EL EL EL EL EL ELd ELW  + GRj  xL 
 '       g   i     ELo; i ELg  + GRj  xL 
 '       g   i     EL; i ELw  + GRj  xL 
 '       g   i     R# ; i EL` ELC EL. EL L L  + GRj  xL 
 '       g   i     L; i L  + GRj  xL 
 '       g   i     R# ; i5i)z3Run the server using HTTP/StreamableHTTP transport.r  zw' requires HTTP transport but mcp.client.streamable_http is not available. Upgrade the mcp package to get HTTP support.r  headersconnect_timeoutNoauth)build_oauth_authz#MCP OAuth setup failed for '%s': %sr!   follow_redirectsTr   g     r@)readauth)http_clientr(   ) _MCP_HTTP_AVAILABLEImportErrorr   r,   rG   _DEFAULT_CONNECT_TIMEOUTru  tools.mcp_oauthr  r  r  r   rs  r:  r  r  r  _MCP_NEW_HTTPhttpxTimeoutr   AsyncClientr
   r   r  rj  r  ro  r  rp  r  r	   )r   r   r  r  r  _oauth_authr  ro   r  r  client_kwargsr  r  r  _get_session_idrj  _http_kwargss   &&               r   	_run_httpMCPServerTask._run_httpj  sD    ""tyyk *? ?  Umvzz),223 **%68PQ ??g%<.IIsJJw$7 >B^^^$..779QS""'E'E151K1K1MO-.=  #D5==)?e=L#M +2i(&(3f% ((9=999[1#OOO T,[Z/ZZZ^e%00222'."22444)"2277999  [Z PO :99 751"L &'2V$,SALAAA F?(VoVVVZa!,,...#*L..000KKOO%..33555 WV BAAU  DdiiQTU0 :O  [249  [ZZZ POOO :999$ B W.05 WVVV BAAAs/  AP$0P$?(L2 'P$9'P$!P$-P$AP$#M'$P$'N< M*N<N	 M-!N	$M<8M09M<M3;M<M6M<N	"M9#N	'N<2N3N<7P$N96P$9O:P$=POPO)	1O2O)	O";O)	O%O)	PO'P P$+P,P$2M$="MM$$P$*N<-N	0M<3M<6M<9N	<NN
NN	N	N<N6"N%#
N6.N60	N<9P$<O	O
O	O	
P$PO)	"O)	%O)	'P)P/O20
P;P=PP$P!	P
P!	P!		P$c                   "   V P                   f   R# V P                   P                  4       G Rj  xL
 p\        VR4      '       d   VP                  V n        R# . V n        R#  L25i)z*Discover tools from the connected session.Nr8  )rj  r  r   r8  rq  )r   r  s   & r   r  MCPServerTask._discover_tools  sY     <<!\\4466 |W--  	  	 7s   .A%A#3A%c                    < V ^8  d   QhRS[ /# r  r4  )r   r   s   "r   r   rh    s     K$ K$ K$r   c           	       "   Wn         VP                  R\        4      V n        VP                  R4      ;'       g    RP	                  4       P                  4       V n        VP                  R/ 4      pVP                  RR4      '       d)   \        '       d   \        V P                  V4      V n
        MRV n
        RV9   d(   R	V9   d!   \        P                  R
V P                  4       ^ pRp  V P                  4       '       d   V P                  V4      G Rj  xL
  MV P                  V4      G Rj  xL
   RV n        R#  L( L  \"         Ed~   pRT n        T P$                  P'                  4       '       g.   YPn        T P$                  P+                  4         Rp?RT n        R# T P,                  P'                  4       '       d/   \        P/                  RT P                  T4        Rp?RT n        R# T^,          pT\0        8  d4   \        P                  RT P                  \0        T4        Rp?RT n        R# \        P                  RT P                  T\0        YE4       \2        P4                  ! T4      G Rj  xL 
  \7        T^,          \8        4      pT P,                  P'                  4       '       d    Rp?RT n        R#  Rp?MRp?ii ; i RT n        EK    RT n        i ; i5i)zLong-lived coroutine: connect, discover tools, wait, disconnect.

Includes automatic reconnection with exponential backoff if the
connection drops unexpectedly (unless shutdown was requested).
r   r  rF   samplingenabledTNr  rQ   zMCP server '%s' has both 'url' and 'command' in config. Using HTTP transport ('url'). Remove 'command' to silence this warning.g      ?z0MCP server '%s' disconnected during shutdown: %szDMCP server '%s' failed after %d reconnection attempts, giving up: %szJMCP server '%s' connection lost (attempt %d/%d), reconnecting in %.0fs: %s)rr  rG   rk  rl  r   r\   ru  r  r   r   rs  r  r   r{  r  r  rj  r  ro  is_setr  r  rp  r   _MAX_RECONNECT_RETRIESrT  sleeprR  _MAX_BACKOFF_SECONDS)r   r   sampling_configretriesbackoffro   s   &&    r   runMCPServerTask.run  s_     "JJy2GH!::f-33::<BBD !**Z4y$//4G4G,TYYHDN!DN F?yF2NN  			 .$==??..000//&111P  $Y 11  %# {{))++"%KKKOO%@  $; ''..00LLJ		3 0  $- 133NN(		#93
   $ 0IIw(>	 mmG,,,gk+?@ ''..00# 1I%H 1  $ts   BK'
K'AK'-*E E E 1E2E 7	K' E E KAKK 	K'$K!K%K )	K'2:K,K 0	K'9A K9I<:<K6K :	K'K KK 
K'	K$$K'c                    < V ^8  d   QhRS[ /# r  r4  )r   r   s   "r   r   rh    s      $ r   c                   "   \         P                  ! V P                  V4      4      V n        V P                  P                  4       G Rj  xL
  V P                  '       d   V P                  hR#  L$5i)z<Create the background Task and wait until ready (or failed).N)rT  ensure_futurer  rm  ro  r  r  )r   r   s   &&r   startMCPServerTask.start  sS     **488F+;<
kk   ;;;++  	!s   AA1
A/A1!A1c                  "   V P                   P                  4        V P                  '       dL   V P                  P                  4       '       g,    \        P
                  ! V P                  ^
R7      G Rj  xL
  RV n        R#  L  \        P                   dp    \        P                  RT P                  4       T P                  P                  4         T P                  G Rj  xL 
   Lr  \        P                   d      Li ; ii ; i5i)z=Signal the Task to exit and wait for clean resource teardown.rI  Nz3MCP server '%s' shutdown timed out, cancelling task)rp  r  rm  donerT  rU  rW  r  r   r   cancelCancelledErrorrj  r9  s   &r   shutdownMCPServerTask.shutdown  s       ":::djjoo//&&tzz2>>>  ?'' 	III 

!!#**$$-- 	sg   AD%B 5B6B :	DB AD	C,$C'%C,*D,DD	DDD		D)ru  rr  r  ro  rw  rt  rs  rp  rm  rq  r   rj  rl  N)r   rj  rl  rm  ro  rp  rq  r  rr  rs  rt  ru  rw  )r   r]  r^  r_  r`  	__slots__r   r{  r  r  r  r  r  r  r  r  rb  rc  rd  s   @r   rf  rf    sn     I, ,% %<"H18 18fJ6 J6X	
K$ K$Z  r   rf  c                $    V ^8  d   QhR\         /# r   )r  )r   s   "r   r   r   <  s      c r   c                 8   \         P                  ! 4       p  RV  RV  R2p\        V4      ;_uu_ 4       pVP                  4       P	                  4        Uu0 uF%  q3P                  4       '       g   K  \        V4      kK'  	  upuuRRR4       # u upi   + '       g   i     M; i  \        \        \        3 d     Mi ; i ^ RI
pTP                  T 4      P                  4        Uu0 uF  qUP                  kK  	  up# u upi   \         d     \        4       # i ; i)zReturn a set of current child process PIDs.

Uses /proc on Linux, falls back to psutil, then empty set.
Used by _run_stdio to identify the subprocess spawned by stdio_client.
z/proc/z/task/z	/childrenN)r/   getpidopenr  rH   r\   r   r~   OSErrorr'   psutilProcesschildrenpidr  r  )my_pidchildren_pathfpr  cs         r   r  r  <  s     YY[F vhi@-  A$%FFHNN$4B$4q	FCF$4B ! B ! w
3 %~~f5>>@A@!@AAA 5Lsj   B$ !BB0B?B
B$ BB!	B$ !B$ $B=<B=%D &C=:D =D DDc                    VP                  R4      p\        V\        4      '       d   R\        V4      9   d   R# V P	                  V4       R# )a  Suppress benign 'Event loop is closed' noise during shutdown.

When the MCP event loop is stopped and closed, httpx/httpcore async
transports may fire __del__ finalizers that call call_soon() on the
dead loop.  asyncio catches that RuntimeError and routes it here.
We silence it because the connection is being torn down anyway; all
other exceptions are forwarded to the default handler.
r  zEvent loop is closedN)rG   r}   RuntimeErrorr<   default_exception_handler)looprX  ro   s   && r   _mcp_loop_exception_handlerr  V  s<     ++k
"C#|$$)?3s8)K""7+r   c                    \         ;_uu_ 4        \        e&   \        P                  4       '       d    RRR4       R# \        P                  ! 4       s\        P                  \        4       \        P                  ! \        P                  RRR7      s
\        P                  4        RRR4       R#   + '       g   i     R# ; i)z>Start the background event loop thread if not already running.Nzmcp-event-loopT)targetr   daemon)r  	_mcp_loop
is_runningrT  new_event_loopset_exception_handlerr  	threadingThreadrun_forever_mcp_threadr  r(   r   r   _ensure_mcp_loopr  e  s     
 Y%9%9%;%; 
 **,	''(CD&&((!

 	 
s   "B0B0A)B00C	c                $    V ^8  d   QhR\         /# )r   r   )r   )r   s   "r   r   r   u  s     * *E *r   c                    \         ;_uu_ 4        \        pRRR4       Xe   VP                  4       '       g   \        R4      h\        P
                  ! W4      pVP                  VR7      #   + '       g   i     L]; i)z@Schedule a coroutine on the MCP event loop and block until done.NzMCP event loop is not runningrI  )r  r  r  r  rT  run_coroutine_threadsafer   )coror   r  futures   &&  r   _run_on_mcp_loopr  u  sW    	 
|4??,,:;;--d9F===)) 
s   A++A;	c                f   \        V \        4      '       d   ^ RIpR pVP                  ! RW 4      # \        V \        4      '       d/   V P                  4        UUu/ uF  w  r4V\        V4      bK  	  upp# \        V \        4      '       d   V  Uu. uF  p\        V4      NK  	  up# V # u uppi u upi )z@Recursively resolve ``${VAR}`` placeholders from ``os.environ``.Nc                 ~    \         P                  P                  V P                  ^4      V P                  ^ 4      4      # )   )r/   r0   rG   r   )ms   &r   _replace'_interpolate_env_vars.<locals>._replace  s'    ::>>!''!*aggaj99r   z\$\{([^}]+)\})r}   r<   r   r?   r,   r1   _interpolate_env_varsr   )r7   r   r  kvs   &    r   r  r    s    %	:vv&88%8=F(++FF%278%Q%a(%88L G8s   B(B.c                F    V ^8  d   QhR\         \        \        3,          /# r   )r   r<   r,   )r   s   "r   r   r     s      $sDy/ r   c                     ^ RI Hp  V ! 4       pVP                  R4      pV'       d   \        V\        4      '       g   / #  ^ RIHp V! 4        VP                  4        UUu/ uF  w  rEV\        V4      bK  	  upp#   \         d     L;i ; iu uppi   \         d#   p\        P                  RT4       / u Rp?# Rp?ii ; i)a  Read ``mcp_servers`` from the Hermes config file.

Returns a dict of ``{server_name: server_config}`` or empty dict.
Server config can contain either ``command``/``args``/``env`` for stdio
transport or ``url``/``headers`` for HTTP transport, plus optional
``timeout``, ``connect_timeout``, and ``auth`` overrides.

``${ENV_VAR}`` placeholders in string values are resolved from
``os.environ`` (which includes ``~/.hermes/.env`` loaded at startup).
)load_configmcp_servers)load_hermes_dotenvzFailed to load MCP config: %sN)hermes_cli.configr  rG   r}   r,   hermes_cli.env_loaderr  r  r1   r  r  r   )r  r   serversr  r   cfgro   s          r   _load_mcp_configr    s    1**]+j$77I	@  CJ--/R/YT+C00/RR  		R 4c:	sR   %B B A< B !B8B <B
B 	B

	B C B;5C ;C c                <    V ^8  d   QhR\         R\        R\        /# r   r   r   r   )r<   r,   rf  )r   s   "r   r   r     s!       T m r   c                Z   "   \        V 4      pVP                  V4      G Rj  xL
  V#  L5i)at  Create an MCPServerTask, start it, and return when ready.

The server Task keeps the connection alive in the background.
Call ``server.shutdown()`` (on the same event loop) to tear it down.

Raises:
    ValueError: if required config keys are missing.
    ImportError: if HTTP transport is needed but not available.
    Exception: on connection or initialization failure.
N)rf  r  )r   r   servers   && r   _connect_serverr     s-      4 F
,,v
M s    +)+c                <    V ^8  d   QhR\         R\         R\        /# )r   r   	tool_namerl  r<   r   )r   s   "r   r   r     s!     1 1C 1C 1u 1r   c                "   a aa R V VV3R llpV# )zReturn a sync handler that calls an MCP tool via the background loop.

The handler conforms to the registry's dispatch interface:
``handler(args_dict, **kwargs) -> str``
c                0    V ^8  d   QhR\         R\        /# r   r)  r   rC   )r   s   "r   r   (_make_tool_handler.<locals>.__annotate__  s     ( (t (# (r   c           
        <a a \         ;_uu_ 4        \        P                  S4      oR R R 4       S'       d   SP                  '       g   \        P
                  ! RRS R2/4      # V VV3R lp \        V! 4       SR7      #   + '       g   i     La; i  \         d]   p\        P                  RSST4       \        P
                  ! R\        R\        T4      P                   RT 24      /4      u R p?# R p?ii ; i)	Nerrorr  ' is not connectedc                  h  <"   SP                   P                  SSR 7      G Rj  xL
 p V P                  '       dq   RpV P                  ;'       g    .  F)  p\	        VR4      '       g   K  WP
                  ,          pK+  	  \        P                  ! R\        T;'       g    R4      /4      # . pV P                  ;'       g    .  F2  p\	        VR4      '       g   K  VP                  VP
                  4       K4  	  \        P                  ! RV'       d   RP                  V4      /4      # R/4      #  EL5i)r   NrF   r:   r)  zMCP tool returned an errorr   r   )rj  	call_toolisErrorr   r   r:   r  r  r@   r   rJ   )r   
error_textr   rN   r)  r  r"  s       r   _call3_make_tool_handler.<locals>._handler.<locals>._call  s     !>>33I3NNF~~~
$nn222Euf--"jj0
 3 zz_"BB&B#    "E ....B.5&))LL, / ::xU5)9KLLKLL% Os8   !D2D/D2D2D2*4D2 D2 D27D2D2rI  zMCP tool %s/%s call failed: %sMCP call failed: : r  _serversrG   rj  r  r  r  r  r  r)  r@   r   r   )r)  kwargsr0  ro   r  r   r"  rl  s   f,  @r   r  $_make_tool_handler.<locals>._handler  s    U\\+.F V^^^::<}4FG  	M*	#EG\BB; U<  		LL0Y ::'S	(:(:';2cUC  		s*   B/B B	C;AC60C;6C;r(   )r   r"  rl  r  s   fff r   _make_tool_handlerr8    s    ( (T Or   c                0    V ^8  d   QhR\         R\        /# r   r   rl  r#  )r   s   "r   r   r     s     ' 'c ' 'r   c                   a a R V V3R llpV# )z>Return a sync handler that lists resources from an MCP server.c                0    V ^8  d   QhR\         R\        /# r&  rC   )r   s   "r   r   2_make_list_resources_handler.<locals>.__annotate__       " "t "# "r   c           
        <a \         ;_uu_ 4        \        P                  S4      oR R R 4       S'       d   SP                  '       g   \        P
                  ! RRS R2/4      # V3R lp \        V! 4       SR7      #   + '       g   i     L_; i  \         d\   p\        P                  RST4       \        P
                  ! R\        R\        T4      P                   RT 24      /4      u R p?# R p?ii ; i)	Nr)  r  r*  c                  `  <"   SP                   P                  4       G R j  xL
 p . p\        V R4      '       d   V P                  M.  F  p/ p\        VR4      '       d   \	        VP
                  4      VR&   \        VR4      '       d   VP                  VR&   \        VR4      '       d"   VP                  '       d   VP                  VR&   \        VR4      '       d"   VP                  '       d   VP                  VR&   VP                  V4       K  	  \        P                  ! RV/4      #  EL
5i)N	resourcesurir   rC  r   )rj  list_resourcesr   rA  r<   rB  r   rC  r   r   r  r  )r   rA  rentryr  s       r   r0  =_make_list_resources_handler.<locals>._handler.<locals>._call  s     !>>88::FI*1&+*F*Ff&&BN1e$$#&quu:E%L1f%%$%FFE&M1m,,+,==E-(1j))ajjj()

E*%  ' O ::{I677 ;s   D.D+BD.<2D./=D.rI  z MCP %s/list_resources failed: %sr2  r3  r4  r)  r6  r0  ro   r  r   rl  s   &,  @r   r  ._make_list_resources_handler.<locals>._handler  s    U\\+.F V^^^::<}4FG  	8 
	#EG\BB1 U2  	LL2K ::'S	(:(:';2cUC  		*   A>,B >B	C7AC2,C72C7r(   r   rl  r  s   ff r   _make_list_resources_handlerrK        " "H Or   c                0    V ^8  d   QhR\         R\        /# r:  r#  )r   s   "r   r   r   $  s     ' 'S ' 'r   c                   a a R V V3R llpV# )zFReturn a sync handler that reads a resource by URI from an MCP server.c                0    V ^8  d   QhR\         R\        /# r&  rC   )r   s   "r   r   1_make_read_resource_handler.<locals>.__annotate__'  r>  r   c           
      Z  <aa \         ;_uu_ 4        \        P                  S4      oR R R 4       S'       d   SP                  '       g   \        P
                  ! RRS R2/4      # V P                  R4      oS'       g   \        P
                  ! RR/4      # VV3R lp \        V! 4       SR7      #   + '       g   i     L; i  \         d\   p\        P                  RST4       \        P
                  ! R\        R	\        T4      P                   R
T 24      /4      u R p?# R p?ii ; i)Nr)  r  r*  rB  z Missing required parameter 'uri'c                    <"   SP                   P                  S4      G R j  xL
 p . p\        V R4      '       d   V P                  M. pV Fn  p\        VR4      '       d   VP	                  VP
                  4       K2  \        VR4      '       g   KF  VP	                  R\        VP                  4       R24       Kp  	  \        P                  ! RV'       d   RP                  V4      /4      # R/4      #  L5i)	Ncontentsr:   blobz[binary data, z bytes]r   r   rF   )rj  read_resourcer   rS  r   r:   r   rT  r  r  rJ   )r   rN   rS  r   r  rB  s       r   r0  <_make_read_resource_handler.<locals>._handler.<locals>._call3  s     !>>77<<F!E*1&**E*Ev2H!5&))LL,UF++LL>#ejj/1B'!JK	 "
 ::xU5)9KLLKLL =s   C5C3A)C5AC5C5rI  zMCP %s/read_resource failed: %sr2  r3  r4  )r)  r6  r0  ro   r  rB  r   rl  s   &,  @@r   r  -_make_read_resource_handler.<locals>._handler'  s    U\\+.F V^^^::<}4FG   hhuo::w(JKLL
	M
	#EG\BB1 U2  	LL1; ::'S	(:(:';2cUC  		s*   B1C 1C	D*AD%D*%D*r(   rJ  s   ff r   _make_read_resource_handlerrX  $  rL  r   c                0    V ^8  d   QhR\         R\        /# r:  r#  )r   s   "r   r   r   N  s     , ,C ,u ,r   c                   a a R V V3R llpV# )z<Return a sync handler that lists prompts from an MCP server.c                0    V ^8  d   QhR\         R\        /# r&  rC   )r   s   "r   r   0_make_list_prompts_handler.<locals>.__annotate__Q  s     ' 't '# 'r   c           
        <a \         ;_uu_ 4        \        P                  S4      oR R R 4       S'       d   SP                  '       g   \        P
                  ! RRS R2/4      # V3R lp \        V! 4       SR7      #   + '       g   i     L_; i  \         d\   p\        P                  RST4       \        P
                  ! R\        R\        T4      P                   RT 24      /4      u R p?# R p?ii ; i)	Nr)  r  r*  c            	        <"   SP                   P                  4       G R j  xL
 p . p\        V R4      '       d   V P                  M.  EF  p/ p\        VR4      '       d   VP                  VR&   \        VR4      '       d"   VP
                  '       d   VP
                  VR&   \        VR4      '       d   VP                  '       d   VP                   Uu. uFg  pRVP                  /\        VR4      '       d!   VP
                  '       d   RVP
                  /M/ C\        VR4      '       d   RVP                  /M/ CNKi  	  upVR&   VP                  V4       EK  	  \        P                  ! RV/4      #  ELTu upi 5i)Npromptsr   rC  r   required)rj  list_promptsr   r_  r   rC  r   r`  r   r  r  )r   r_  r  rE  ar  s        r   r0  ;_make_list_prompts_handler.<locals>._handler.<locals>._callY  sK    !>>6688FG(/	(B(BfnnJ1f%%$%FFE&M1m,,+,==E-(1k**q{{{ "#* "-A	 #AFFAHMAZAZ_`_l_l_lq}}=rt <C1j;Q;Q
AJJ7WY
 "-*E+& u% K  ::y'233% 9*s4   E=E5A0E=2E=E=5E8
7E85E=8E=rI  zMCP %s/list_prompts failed: %sr2  r3  r4  rG  s   &,  @r   r  ,_make_list_prompts_handler.<locals>._handlerQ  s    U\\+.F V^^^::<}4FG  	4*
	#EG\BB; U<  	LL0+s ::'S	(:(:';2cUC  		rI  r(   rJ  s   ff r   _make_list_prompts_handlerre  N  s    ' 'R Or   c                0    V ^8  d   QhR\         R\        /# r:  r#  )r   s   "r   r   r   }  s     2 2# 2U 2r   c                   a a R V V3R llpV# )zDReturn a sync handler that gets a prompt by name from an MCP server.c                0    V ^8  d   QhR\         R\        /# r&  rC   )r   s   "r   r   ._make_get_prompt_handler.<locals>.__annotate__  s     - -t -# -r   c           
        <aaa \         ;_uu_ 4        \        P                  S4      oR R R 4       S'       d   SP                  '       g   \        P
                  ! RRS R2/4      # V P                  R4      oS'       g   \        P
                  ! RR/4      # V P                  R/ 4      oVVV3R lp \        V! 4       SR7      #   + '       g   i     L; i  \         d\   p\        P                  R	ST4       \        P
                  ! R\        R
\        T4      P                   RT 24      /4      u R p?# R p?ii ; i)Nr)  r  r*  r   z!Missing required parameter 'name'r   c                    <"   SP                   P                  SSR 7      G Rj  xL
 p . p\        V R4      '       d   V P                  M.  F  p/ p\        VR4      '       d   VP                  VR&   \        VR4      '       dX   VP
                  p\        VR4      '       d   VP                  VR&   M)\        V\        4      '       d   WCR&   M\        V4      VR&   VP                  V4       K  	  RV/p\        V R4      '       d"   V P                  '       d   V P                  VR&   \        P                  ! V4      #  EL5i)r,  Nr   r   r   r:   rC  )rj  
get_promptr   r   r   r   r:   r}   r<   r   rC  r  r  )	r   r   r  rE  r   respr   r   r  s	         r   r0  9_make_get_prompt_handler.<locals>._handler.<locals>._call  s    !>>44TY4OOFH+26:+F+FBN3''$'HHE&M3	**!kkGw//+2<<i(#GS11+2i(+.w<i(& O )Dv}--&2D2D2D&,&8&8]#::d##' Ps   !D?D<C1D?&D?rI  zMCP %s/get_prompt failed: %sr2  r3  r4  )	r)  r6  r0  ro   r   r   r  r   rl  s	   &,  @@@r   r  *_make_get_prompt_handler.<locals>._handler  s   U\\+.F V^^^::<}4FG   xx::w(KLMMHH["-		$,
	#EG\BBG UH  	LL.S ::'S	(:(:';2cUC  		s*   C3C C	D>#AD93D>9D>r(   rJ  s   ff r   _make_get_prompt_handlerrp  }  s    - -^ Or   c                $    V ^8  d   QhR\         /# )r   r   r;   )r   s   "r   r   r     s       r   c                   a  R V 3R llpV# )zBReturn a check function that verifies the MCP connection is alive.c                $    V ^8  d   QhR\         /# r   r   )r   s   "r   r   $_make_check_fn.<locals>.__annotate__  s     A AD Ar   c                     < \         ;_uu_ 4        \        P                  S4      p R R R 4       X R J;'       d    V P                  R J#   + '       g   i     L*; iN)r  r5  rG   rj  )r  r   s    r   _check_make_check_fn.<locals>._check  s?    U\\+.F T!@@fnnD&@@ Us   AA	r(   )r   rw  s   f r   _make_check_fnry    s    A A
 Mr   c                >    V ^8  d   QhR\         R,          R\         /# )r   schemaNr   r4  )r   s   "r   r   r     s      t  r   c                l    V '       g   RRR/ /# V P                  R4      R8X  d   RV 9  d	   / V CR/ /C# V # )z?Normalize MCP input schemas for LLM tool-calling compatibility.r   object
properties)rG   )r{  s   &r   rS  rS    sC    ,33zz&X%,f*D+&+,++Mr   c                0    V ^8  d   QhR\         R\         /# )r   r7   r   r;   )r   s   "r   r   r     s     ; ;s ;s ;r   c                X    \         P                  ! RR\        T ;'       g    R4      4      # )a!  Return an MCP name component safe for tool and prefix generation.

Preserves Hermes's historical behavior of converting hyphens to
underscores, and also replaces any other character outside
``[A-Za-z0-9_]`` with ``_`` so generated tool names are compatible with
provider validation rules.
z[^A-Za-z0-9_]_rF   )r   r?   r<   )r7   s   &r   sanitize_mcp_name_componentr    s#     66"CU[[b)9::r   c                0    V ^8  d   QhR\         R\        /# r   r   r   r   )r   s   "r   r   r     s      S t r   c                    \        VP                  4      p\        V 4      pRV RV 2pRTRVP                  ;'       g    RVP                   RV  2R\        VP                  4      /# )a7  Convert an MCP tool listing to the Hermes registry schema format.

Args:
    server_name: The logical server name for prefixing.
    mcp_tool:    An MCP ``Tool`` object with ``.name``, ``.description``,
                 and ``.inputSchema``.

Returns:
    A dict suitable for ``registry.register(schema=...)``.
mcp_r  r   rC  z	MCP tool z from r%   )r  r   rC  rS  rD  )r   mcp_toolsafe_tool_namesafe_server_namer  s   &&   r   _convert_mcp_schemar    sw     1?N2;?+,An-=>Mx++]]8==/P[}/]1(2F2FG r   c                T    V ^8  d   QhR\         \        \        ,          ,          RR/# )r   server_namesr   N)r   r   r<   )r   s   "r   r   r     s#     0. 0.Xd3i%8 0.D 0.r   c                  a
 ^ RI Hp V f"   \        \        4       P	                  4       4      p \        4       p. pV  F  pR\        V4       R2o
\        V
3R lV 4       4      pVP                  V4       VP                  V4      pV'       dI   \        VP                  RR4      4      P                  R4      '       g   \        P                  R	V4       K  RRV R
2RVR. /W&   K  	  VP                  4        FN  w  rxVP                  R4      '       g   K  V F*  p	WR,          9  g   K  VR,          P                  V	4       K,  	  KP  	  R# )a  Expose each MCP server as a standalone toolset and inject into hermes-* sets.

Creates a real toolset entry in TOOLSETS for each server name (e.g.
TOOLSETS["github"] = {"tools": ["mcp_github_list_files", ...]}). This
makes raw server names resolvable in platform_toolsets overrides.

Also injects all MCP tools into hermes-* umbrella toolsets for the
default behavior.

Skips server names that collide with built-in toolsets.
r  Nr  r  c              3   X   <"   T F  qP                  S4      '       g   K  Vx  K!  	  R # 5irv  )r3   )r   r   safe_prefixs   & r   r   %_sync_mcp_toolsets.<locals>.<genexpr>  s      
!<<#<AAxs   *
*rC  rF   r  uM   Skipping MCP toolset alias '%s' — a built-in toolset already uses that namez' toolsr8  includesr  )r  r  r   r  keys_existing_tool_namesr  sortedr   rG   r<   r3   r  r   r1   r   )r  r  rL   all_mcp_toolsr   rY  existing_tsr  r  r"  r  s   &         @r   _sync_mcp_toolsetsr    s6    ",.3356#%H!M#8EFaH 

 
 	\* ll;/s;??="#EFQQR`aaNN_  \+g>\!
! $.  ~~'!!),,&I7+7""9- ' (r   c                F    V ^8  d   QhR\         R\        \        ,          /# r  )r<   r   r,   )r   s   "r   r   r     s"     D D DT
 Dr   c                   \        V 4      pRRRV R2RRV  R2RR	R
R/ //RR/RRRV R2RRV  R2RR	R
RRR	RRR//RR.//RR/RRRV R2RRV  R2RR	R
R/ //RR/RRRV R2RRV  R2RR	R
RRR	RRR/RR	R
RR//RR.//RR/.# )zBuild schemas for the MCP utility tools (resources & prompts).

Returns a list of (schema, handler_factory_name) tuples encoded as dicts
with keys: schema, handler_key.
r{  r   r  _list_resourcesrC  z*List available resources from MCP server 'r   r%   r   r}  r~  handler_keyrC  _read_resourcez(Read a resource by URI from MCP server 'rB  stringzURI of the resource to readr`  rU  _list_promptsz(List available prompts from MCP server 'ra  _get_promptz&Get a prompt by name from MCP server 'zName of the prompt to retriever   z(Optional arguments to pass to the promptrl  )r  )r   	safe_names   & r   _build_utility_schemasr    s    ,K8I $yk9!KK=XYZH " +
	
 $yk8!I+VWXH "H)+H # 	 ?	
$ $yk7!I+VWXH " >
	
 $yk5!G}TUVH "H)+K! $"H)+U&	# $ <'	
Q= =r   c                R    V ^8  d   QhR\         R\        R\        \        ,          /# )r   r7   labelr   )r   r<   r  )r   s   "r   r   r   f  s%     	 	# 	c 	c#h 	r   c                   V f   \        4       # \        V \        4      '       d   V 0# \        V \        \        \         34      '       d   V  Uu0 uF  p\        V4      kK  	  up# \
        P                  RW4       \        4       # u upi )z8Normalize include/exclude config to a set of tool names.z>MCP config %s must be a string or list of strings; ignoring %r)r  r}   r<   r   rR   r  r   )r7   r  r   s   && r   _normalize_name_filterr  f  sk    }u%w%$s+,,&+,edD	e,,
NNSUZb5L -s   Bc                <    V ^8  d   QhR\         R\        R\        /# )r   r7   r   r   )r   r   )r   s   "r   r   r   r  s!      #   r   c                    V f   V# \        V \        4      '       d   V # \        V \        4      '       d1   V P                  4       P	                  4       pVR9   d   R# VR9   d   R# \
        P                  RW4       V# )z2Parse a bool-like config value with safe fallback.TFzAMCP config expected a boolean-ish value, got %r; using default=%s>   1onyestrue>   0noofffalse)r}   r   r<   r\   r   r  r   )r7   r   lowereds   && r   _parse_boolishr  r  sl    }%%++-%%'0011
NNVX]gNr   rC  rU  ra  rl  c          	      ^    V ^8  d   QhR\         R\        R\        R\        \        ,          /# )r   r   r  r   r   r<   rf  r,   r   )r   s   "r   r   r     s/       m T VZ[_V` r   c                *   VP                  R4      ;'       g    / p\        VP                  R4      RR7      p\        VP                  R4      RR7      p. p\        V 4       F  pVR,          pVR
9   d!   V'       g   \        P	                  RW4       K3  VR9   d!   V'       g   \        P	                  RW4       KZ  \
        V,          p	\        VP                  V	4      '       g   \        P	                  R	V VV	4       K  VP                  V4       K  	  V# )z?Select utility schemas based on config and server capabilities.r8  rA  Tr   r_  r  z;MCP server '%s': skipping utility '%s' (resources disabled)z9MCP server '%s': skipping utility '%s' (prompts disabled)z9MCP server '%s': skipping utility '%s' (session lacks %s)>   rU  rC  >   rl  ra  )	rG   r  r  r  r   _UTILITY_CAPABILITY_METHODSr   rj  r   )
r   r  r   tools_filterresources_enabledprompts_enabledselectedrE  r  required_methods
   &&&       r   _select_utility_schemasr    s    ::g&,,"L&|'7'7'DdS$\%5%5i%@$OOH'4M*==FWLLVXcq88LLTVao5kBv~~77LLK	 % 5& Or   c                :    V ^8  d   QhR\         \        ,          /# r   r   r<   )r   s   "r   r   r     s     
 
d3i 
r   c                 $   . p \         P                  4        Fu  w  r\        VR4      '       d   V P                  VP                  4       K4  VP
                   F1  p\        VP                  V4      pV P                  VR,          4       K3  	  Kw  	  V # )z6Return tool names for all currently connected servers.rt  r   )	r5  r1   r   r   rt  rq  r  r   r   )names_snamer  r  r{  s        r   r  r    sr    E"..*6344LL667H(h?FLL( &	 + Lr   c          	      ^    V ^8  d   QhR\         R\        R\        R\        \         ,          /# )r   r   r  r   r   r  )r   s   "r   r   r     s3     p p pm pT pdSVi pr   c                  aa ^ RI Hp ^ RIHpHp . pRV  2pVP                  R4      ;'       g    / p\        VP                  R4      RV  R24      o\        VP                  R4      RV  R	24      oR
 VV3R llp	VP                   F  p
V	! V
P                  4      '       g#   \        P                  RW
P                  4       K=  \        W
4      pVR,          pVP                  V4      pV'       d;   VP                  R4      '       g$   \        P                  RW
P                  W4       K  VP                  VVV\!        W
P                  VP"                  4      \%        V 4      RVR,          R7       VP'                  V4       K  	  R\(        R\*        R\,        R\.        /p\%        V 4      p\1        WV4       F  pVR,          pVR,          pVV,          ! WP"                  4      pVR,          pVP                  V4      pV'       d2   VP                  R4      '       g   \        P                  RV VV4       K  VP                  VVVVVRVR,          R7       VP'                  V4       K  	  V'       dt   V! VRV  R2VR7       VP3                  4        FP  w  ppVP                  R4      '       g   K  V F+  pVVR,          9  g   K  VR,          P'                  V4       K-  	  KR  	  V# )a+  Register tools from an already-connected server into the registry.

Handles include/exclude filtering, utility tools, toolset creation,
and hermes-* umbrella toolset injection.

Used by both initial discovery and dynamic refresh (list_changed).

Returns:
    List of registered prefixed tool names.
r  )create_custom_toolsetr  zmcp-r8  includezmcp_servers.z.tools.includeexcludez.tools.excludec                0    V ^8  d   QhR\         R\        /# )r   r"  r   )r<   r   )r   s   "r   r   ,_register_server_tools.<locals>.__annotate__  s      C D r   c                 <   < S'       d   V S9   # S'       d   V S9  # R # Tr(   )r"  exclude_setinclude_sets   &r   _should_register0_register_server_tools.<locals>._should_register  s"    ++K//r   z8MCP server '%s': skipping tool '%s' (filtered by config)r   us   MCP server '%s': tool '%s' (→ '%s') collides with built-in tool in toolset '%s' — skipping to preserve built-inFrC  )r   toolsetr{  handlercheck_fnis_asyncrC  rC  rU  ra  rl  r{  r  up   MCP server '%s': utility tool '%s' collides with built-in tool in toolset '%s' — skipping to preserve built-inzMCP tools from z server)r   rC  r8  r  )r  r  r  r  r  rG   r  rq  r   r  r   r  get_toolset_for_toolr3   r   registerr8  rl  ry  r   rK  rX  re  rp  r  r1   )r   r  r   r  r  r  registered_namestoolset_namer  r  r  r{  tool_name_prefixedexisting_toolset_handler_factoriesr  rE  r  r  	util_namer  r  r"  r  r  s   &&&                    @@r   r  r    s    (8"$$=L ::g&,,"L()9)9))DUYTZZhFijK()9)9))DUYTZZhFijK  MM..LLSUY[h[hi$T4#F^ $889KL$4$?$?$G$GNNImm%7
 # &t]]F<O<OP#D)}- 	 	
 	 235 "> 	642.	 d#H(v>xM*$[1$8K8KL6N	 $88C$4$?$?$G$GNNIi!1
  }- 	 	
 		*3 ?8 )$w7"	
 $>>+KGR!!),,!1I 737**95 "2 , r   c                R    V ^8  d   QhR\         R\        R\        \         ,          /# r  r<   r,   r   )r   s   "r   r   r   '  s%      c 4 DI r   c           	       "   VP                  R\        4      p\        P                  ! \	        W4      VR7      G Rj  xL
 p\
        ;_uu_ 4        V\        V &   RRR4       \        WV4      p\        V4      Vn	        RV9   d   RMRp\        P                  RW\        V4      RP                  V4      4       V#  L}  + '       g   i     Lk; i5i)	zkConnect to a single MCP server, discover tools, and register them.

Returns list of registered tool names.
r  rI  Nr  HTTPstdioz/MCP server '%s' (%s): registered %d tool(s): %sr?  )rG   r  rT  rU  r   r  r5  r  r   rt  r  r   r   rJ   )r   r   r  r  r  transport_types   &&    r   _discover_and_register_serverr  '  s     
 jj!24LMO##% F 
 
 .dFC$()9$:F!$VGN
KK9c"23		"#
 ! 
s(   ;CB;C
B=A"C=C	Cc                h    V ^8  d   QhR\         \        \        3,          R\        \        ,          /# )r   r  r   )r   r<   r,   r   )r   s   "r   r   r   D  s*     M" M"$sDy/ M"d3i M"r   c                4  a	a
 \         '       g   \        P                  R4       . # V '       g   \        P                  R4       . # \        ;_uu_ 4        V P	                  4        UUu/ uF9  w  rV\
        9  g   K  \        VP                  RR4      RR7      '       g   K7  WbK;  	  uppo
RRR4       S
'       g-   \        \        V P                  4       4      4       \        4       # \        4        R R lo	V	V
3R	 lp\        V! 4       ^xR
7       \        \        V P                  4       4      4       \        ;_uu_ 4        S
 Uu. uF  qD\
        9   g   K  VNK  	  pp\        R V 4       4      pRRR4       \        S
4      \        X4      ,
          pX'       g	   V'       d=   RV R\        V4       R2pV'       d   VRV R2,          p\        P!                  V4       \        4       # u uppi   + '       g   i     ELM; iu upi   + '       g   i     L; i)a?  Connect to explicit MCP servers and register their tools.

Idempotent for already-connected server names. Servers with
``enabled: false`` are skipped without disconnecting existing sessions.

Args:
    servers: Mapping of ``{server_name: server_config}``.

Returns:
    List of all currently registered MCP tool names.
z;MCP SDK not available -- skipping explicit MCP registrationz No explicit MCP servers providedr  Tr  Nc                R    V ^8  d   QhR\         R\        R\        \         ,          /# )r   r   r  r   r  )r   s   "r   r   *register_mcp_servers.<locals>.__annotate__h  s%     > ># >D >T#Y >r   c                4   "   \        W4      G Rj  xL
 #  L5i)z@Connect to a single server and return its registered tool names.N)r  )r   r  s   &&r   _discover_one+register_mcp_servers.<locals>._discover_oneh  s     24====s   c            	        <"   \        SP                  4       4      p \        P                  ! V3R  lSP	                  4        4       RR/ G Rj  xL
 p\        W4       Fm  w  r#\        V\        4      '       g   K  SP                  V/ 4      P                  R4      p\        P                  RTV'       d   RV R2MR\        V4      4       Ko  	  R#  L5i)	c              3   8   <"   T F  w  rS! W4      x  K  	  R # 5irv  r(   )r   r   r  r  s   &  r   r   >register_mcp_servers.<locals>._discover_all.<locals>.<genexpr>p  s     L8K94mD&&8Ks   return_exceptionsTNrQ   z*Failed to connect to MCP server '%s'%s: %sz
 (command=)rF   )r   r  rT  gatherr1   zipr}   r  rG   r  r   r   )r  resultsr   r   rQ   r  new_serverss        r   _discover_all+register_mcp_servers.<locals>._discover_alll  s     K,,./L8I8I8KL
"
 
  6LD&),,%//$377	B@/6j	+B)&1	 7	
s   ACC'C;9C5CrI  c              3   d   "   T F&  p\        \        \        V,          R . 4      4      x  K(  	  R# 5irt  Nr   r|   r5  )r   ns   & r   r   'register_mcp_servers.<locals>.<genexpr>  s-      
 %=rBCC   .0zMCP: registered  tool(s) from 
 server(s) ( failed))r"   r  r   r  r1   r5  r  rG   r  r   r  r  r  r  sumr   r   )r  r  r  r  r  	connectednew_tool_countfailedsummaryr  r  s   &        @@r   register_mcp_serversr  D  s    >RS	78	 
  
'  %3AEE)T4JTX%Y%Y AD'
 
 4/0#%% >& ]_c2tGLLN+, 
 +=1H}QQ	= 

 
 
 I.F$^$4N3y>BRR\]F88,,GG!!m
 
V > 
sN   G.'G(
; G(
 G(
&G.?HHHH(G..G?	HH	c                :    V ^8  d   QhR\         \        ,          /# r   r  )r   s   "r   r   r     s     . .DI .r   c                 \   \         '       g   \        P                  R4       . # \        4       p V '       g   \        P                  R4       . # \        ;_uu_ 4        V P                  4        UUu. uF9  w  rV\        9  g   K  \        VP                  RR4      RR7      '       g   K7  VNK;  	  pppRRR4       \        V 4      pX'       g   V# \        ;_uu_ 4        V Uu. uF  q\        9   g   K  VNK  	  pp\        R V 4       4      pRRR4       \        V4      \        X4      ,
          pX'       g	   V'       d=   RV R	\        V4       R
2pV'       d   VRV R2,          p\        P                  V4       V# u uppi   + '       g   i     L; iu upi   + '       g   i     L; i)ar  Entry point: load config, connect to MCP servers, register tools.

Called from ``model_tools._discover_tools()``. Safe to call even when
the ``mcp`` package is not installed (returns empty list).

Idempotent for already-connected servers. If some servers failed on a
previous call, only the missing ones are retried.

Returns:
    List of all registered MCP tool names.
z4MCP SDK not available -- skipping MCP tool discoveryzNo MCP servers configuredr  Tr  Nc              3   d   "   T F&  p\        \        \        V,          R . 4      4      x  K(  	  R# 5ir  r  )r   r   s   & r   r   %discover_mcp_tools.<locals>.<genexpr>  s-      
. (@"EFF.r  z  MCP: r  r  r  r  )r"   r  r   r  r  r1   r5  r  rG   r  r  r   r   )	r  r   r  new_server_names
tool_namesconnected_server_namesr   failed_countr  s	            r   discover_mcp_toolsr    sd    >KL	 G01		 %]]_
,	8# (6swwy$7OY](^(^ D, 	 
 
 &g.J	3C!X3C4xGW$$3C!X 
.
 
 
 '(3/E+FFLN+>#>T:U9VV`aL>22GG1
 
 "Y 
sN   F/E=
 E=
(E=
.FF!F2F8F=FF	FF+	c                :    V ^8  d   QhR\         \        ,          /# r   r   )r   s   "r   r   r     s     % %T
 %r   c                    . p \        4       pV'       g   V # \        ;_uu_ 4        \        \        4      pRRR4       VP	                  4        F  w  r4RV9   d   RMRpXP                  V4      pV'       d   VP                  e   RTRTR\        VR4      '       d   \        VP                  4      M\        VP                  4      R	R
/pVP                  '       d#   \        VP                  P                  4      VR&   V P                  V4       K  V P                  RVRVR^ R	R/4       K  	  V #   + '       g   i     EL; i)zReturn status of all configured MCP servers for banner display.

Returns a list of dicts with keys: name, transport, tools, connected.
Includes both successfully connected servers and configured-but-failed ones.
Nr  httpr  r   	transportr8  rt  r  Tr  F)r  r  r,   r5  r1   rG   rj  r   r   rt  rq  rs  r   r   )r   
configuredactive_serversr   r  r  r  rE  s           r   get_mcp_statusr    s    F "#J	h 
  %%'	#slF	##D)fnn0YwvOg?h?hV::;nqrxrr  oAT	E $()9)9)A)A$Bj!MM% MMYU	  (* M1 
s   D..D?	c                \    V ^8  d   QhR\         \        \        \        ,          3,          /# r   )r   r<   r   rR   )r   s   "r   r   r     s!     ? ?S$u+%5 6 ?r   c            
       aaa \         '       g   / # \        4       p V '       g   / # V P                  4        UUu/ uF,  w  r\        VP	                  RR4      RR7      '       g   K*  WbK.  	  uppoS'       g   / # \        4        / o. oVVV3R lp \        V! 4       ^xR7       \        4        S# u uppi   \         d!   p\        P                  RT4        Rp?L8Rp?ii ; i  \        4        i ; i)u  Temporarily connect to configured MCP servers and list their tools.

Designed for ``hermes tools`` interactive configuration — connects to each
enabled server, grabs tool names and descriptions, then disconnects.
Does NOT register tools in the Hermes registry.

Returns:
    Dict mapping server name to list of (tool_name, description) tuples.
    Servers that fail to connect are omitted from the result.
r  Tr  c            	        <"   \        S
P                  4       4      p . pS
P                  4        FK  w  r#VP                  R \        4      pVP                  \        P                  ! \        W#4      VR7      4       KM  	  \        P                  ! VRR/ G Rj  xL
 p\        W4       F  w  r&\        V\        4      '       d   \        P                  RW&4       K3  SP                  V4       . pVP                   F7  p\!        VRR4      ;'       g    Rp	VP                  VP"                  V	34       K9  	  VSV&   K  	  \        P                  ! R S 4       RR/ G Rj  xL
  R#  L L5i)	r  rI  r  TNz$Probe: failed to connect to '%s': %srC  rF   c              3   @   "   T F  qP                  4       x  K  	  R # 5irv  r  )r   ss   & r   r   =probe_mcp_server_tools.<locals>._probe_all.<locals>.<genexpr>!  s     3NqjjllN   )r   r  r1   rG   r  r   rT  rU  r   r  r  r}   r  r  r   rq  r|   r   )r  corosr   r  ctoutcomesoutcomer8  r   descr  probed_serversr   s             r   
_probe_all*probe_mcp_server_tools.<locals>._probe_all  s.    W\\^$ ID*,DEBLL))/$*DbQR ) !G$GG 1MD'9--CTS!!'*E^^q-4::affd^, $ !F4L 2 nn3N3
"
 	
 	
 H	
s,   BE(E$A7E(AE(E&E(&E(rI  zMCP probe failed: %sN)r"   r  r1   r  rG   r  r  r  r  r   _stop_mcp_loop)servers_configr  r  r"  ro   r  r!  r   s        @@@r   probe_mcp_server_toolsr&    s     >	%'N	 (--//!%%	40$?? 	/G 	%'F*,N
4s3 	M[P  2+S112 	s5   'B.%B.B4 4C?CC" CC" "C.c                 ,  a \         ;_uu_ 4        \        \        P                  4       4      oRRR4       S'       g   \	        4        R# V3R lp \         ;_uu_ 4        \
        pRRR4       XeF   VP                  4       '       d0    \        P                  ! V ! 4       V4      pVP                  ^R7       \	        4        R#   + '       g   i     L; i  + '       g   i     Lx; i  \         d!   p\        P                  RT4        Rp?LXRp?ii ; i)a  Close all MCP server connections and stop the background loop.

Each server Task is signalled to exit its ``async with`` block so that
the anyio cancel-scope cleanup happens in the same Task that opened it.
All servers are shut down in parallel via ``asyncio.gather``.
Nc                  x  <"   \         P                  ! R  S 4       RR/ G Rj  xL
 p \        SV 4       F>  w  r\        V\        4      '       g   K  \
        P                  RVP                  V4       K@  	  \        ;_uu_ 4        \        P                  4        RRR4       R#  L  + '       g   i     R# ; i5i)c              3   @   "   T F  qP                  4       x  K  	  R # 5irv  r  )r   r  s   & r   r   :shutdown_mcp_servers.<locals>._shutdown.<locals>.<genexpr>@  s     ?.>Foo.>r  r  TNz!Error closing MCP server '%s': %s)rT  r  r  r}   r  r  r   r   r  r5  clear)r  r  r   servers_snapshots      r   	_shutdown'shutdown_mcp_servers.<locals>._shutdown>  s     ?.>?
"
 
 ""2G<NF&),,7f =
 UNN U
 UUs-   !B:B$(B:3B:B&B:&B7	1	B:rI  zError during MCP shutdown: %s)r  r   r5  valuesr$  r  r  rT  r  r   r  r  r   )r-  r  r  ro   r,  s       @r   shutdown_mcp_serversr0  /  s     
 12 
  
 
DOO--	?55ik4HFMM"M% = 
* 
  	?LL8#>>	?s/   CC.C( C	C%	(D3DDc                    V ^8  d   QhRR/# )r   r   Nr(   )r   s   "r   r   r   W  s      T r   c                 b   ^ RI p \        ;_uu_ 4        \        \        4      p\        P	                  4        RRR4       X F:  p \
        P                  ! W P                  4       \        P                  RV4       K<  	  R#   + '       g   i     LR; i  \        \        \        3 d     Kl  i ; i)uf  Best-effort kill of MCP stdio subprocesses that survived loop shutdown.

After the MCP event loop is stopped, stdio server subprocesses *should*
have been terminated by the SDK's context-manager cleanup.  If the loop
was stuck or the shutdown timed out, orphaned children may remain.

Only kills PIDs tracked in ``_stdio_pids`` — never arbitrary children.
Nz*Force-killed orphaned MCP stdio process %d)signalr  r   r  r+  r/   killSIGKILLr  r   ProcessLookupErrorPermissionErrorr  )_signalpidsr  s      r   _kill_orphaned_mcp_childrenr:  W  sy     	K  
 	GGC)LLEsK 	 
 #OW= 		s   $B6BB	B.-B.c                 @   \         ;_uu_ 4        \        p \        pRsRsRRR4       X eO   V P                  V P                  4       Xe   VP                  ^R7        V P                  4        \        4        R# R#   + '       g   i     Ld; i  \         d     L.i ; i)z3Stop the background event loop and join its thread.NrI  )	r  r  r  call_soon_threadsafer   rJ   closer  r:  )r  threads     r   r$  r$  n  s     
		 

 !!$)),KKK"	JJL
 	$%  
  		s   A<B <B	BBc                .   V ^8  d   Qh/ ^ \         9   d   \        \        \        3,          ;R&   ^\         9   d   \        \
        P                  ,          ;R&   ^\         9   d   \        \        P                  ,          ;R&   ^\         9   d
   \        ;R&   # )r   r5  r  r  r  )
__conditional_annotations__r   r<   rf  r   rT  AbstractEventLoopr  r  r  )r   s   "r   r   r      s     D DT! ( '$sM!
" 'U!DZ! 6 58G--. 5[!D\! / .Xi&&' .]!Dn!  S o!Dr   >   HOMELANGrE   TERMUSERSHELLLC_ALLTMPDIR)   rv  r  )kr@  r`  rT  r#   r  r   r   r/   r   r^   r  r   typingr   r   r   r   	getLoggerr   r  r"   r  r  r  r  rF  r   r   mcp.client.stdior   mcp.client.streamable_httpr	   r  r
   r  	mcp.typesr   r   r   r   r   r   r   r   r   r   r   r   r)   rk  r  r  r  	frozensetr2   compile
IGNORECASEr>   r8   r@   rO   rm   r   r   r   r   rf  r5  r  r  rv  r  r  r  r  r  r  r  r  r  r   r8  rK  rX  re  rp  ry  rS  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r&  r0  r:  r$  r   )r@  s   @r   <module>rR     s  DL      	 	    , ,			8	$    !& *K8-N$D"
EN	
 	
 	
 #	`	
 	
 #' "@!A 8
LL^_        
 jj		 MM &&7!*H93@ *-a l9 l9fU Ux
 &( ' 26	 5*. . 	 5 4, *B(1h'T'T,^2j;*0.fDN	" &_N,	 :
pf:M"`.b%P?D%P.&Q@  $#$    NLMN  `^_` K
LLIJKs   I? +H! 4H1 =I I  !	H.*I? -H..I? 1	H>:I? =H>>I? II? II?  I<8I? ;I<<I? ?JJ