
    `gj                        U d Z ddlZddlZddlZddlZddlZddlmZmZm	Z	m
Z
mZ ddlmZmZmZmZmZmZ ddlmZmZ ddlmZ ddlmZ ddlmZmZ  ej:                  e      Z ej@                  d	      Z!d
d
dgdiZ"ee#ee#ef   f   e$d<   de#fdZ%de#fdZ&de#fdZ'de#fdZ(dAde#de)de)fdZ*dddde#de)de#de#fdZ+dBde
e,   dee#e,f   fdZ-de#de,de)fd Z.de#de)fd!Z/d"ee#e,f   dee)e	e#   f   fd#Z0de#de#fd$Z1d%e#de#fd&Z2de#dee#e#f   fd'Z3de#d%e#dee#e#f   fd(Z4d)e
e	e#      dee#e#f   fd*Z5de#d+e
e#   d,e
e#   d-e
e#   d.e	e#   dee#ef   de6e
e#   e
e#   e	e#   e)f   fd/Z7de,de,fd0Z8	 dBdd1de#de,d2e
e9   d3e
e,   de	ee#e#f      f
d4Z:de#de)fd5Z;d6e<de=fd7Z>d8 Z?d9 Z@dBd:ZAd; ZBde#de,de)fd<ZCd= ZDd> ZEd? ZFd@ ZGy)Cu@  
MCP Server Management CLI — ``hermes mcp`` subcommand.

Implements ``hermes mcp add/remove/list/test/configure`` for interactive
MCP server lifecycle management (issue #690 Phase 2).

Relies on tools/mcp_tool.py for connection/discovery and keeps
configuration in ~/.hermes/config.yaml under the ``mcp_servers`` key.
    N)AnyDictListOptionalTuple)cfg_getload_configsave_configget_env_valuesave_env_valueget_hermes_home)Colorscolor)display_hermes_home)validate_mcp_server_entry)_ENV_VAR_PATTERN_env_ref_namez^[A-Za-z_][A-Za-z0-9_]*$codexz
mcp-server)commandargs_MCP_PRESETStextc                 P    t        t        d|  t        j                               y )N  )printr   r   DIMr   s    H/root/.hermes/venv/lib/python3.12/site-packages/hermes_cli/mcp_config.py_infor   .   s    	%"TFVZZ
()    c                 P    t        t        d|  t        j                               y )Nu     ✓ )r   r   r   GREENr   s    r   _successr#   1   s    	%&
./r    c                 P    t        t        d|  t        j                               y )Nu     ⚠ )r   r   r   YELLOWr   s    r   _warningr&   4   s    	%&
/0r    c                 P    t        t        d|  t        j                               y )Nu     ✗ )r   r   r   REDr   s    r   _errorr)   7   s    	%&
,-r    questiondefaultreturnc           	          |rdnd}	 t        t        d|  d| dt        j                              j	                         j                         }|s|S |dv S # t        t        f$ r t                |cY S w xY w)NzY/nzy/Nr   z [z]: >   yyes)	inputr   r   r%   striplowerKeyboardInterruptEOFErrorr   )r*   r+   default_strvals       r   _confirmr7   ;   s{    "%KEBxj;-s;V]]KLRRTZZ\ , x( s   AA A43A4F )passwordr+   r9   c                $    ddl m}  || ||      S )Nr   )prompt)r+   r9   )hermes_cli.cli_outputr;   )r*   r9   r+   _shared_prompts       r   _promptr>   G   s    >(GhGGr    configc                 h    | 
t               } | j                  d      }|rt        |t              si S |S )z;Return the ``mcp_servers`` dict from config, or empty dict.mcp_servers)r	   get
isinstancedict)r?   serverss     r   _get_mcp_serversrF   N   s2    ~jj'G*Wd3	Nr    nameserver_configc                     t        | |      }|r"|D ]  }t        |        t        d|  d       yt               }||j                  di       | <   t	        |       y)a  Add or update a server entry in config.yaml.

    Returns False when a high-signal exfiltration-shaped stdio command is
    rejected. MCP stdio servers are user-chosen local commands, so this blocks
    shell+egress payloads rather than whitelisting command families.
    Server '0' was NOT saved due to suspicious configuration.FrA   T)r   r&   r	   
setdefaultr
   )rG   rH   issuesissuer?   s        r   _save_mcp_serverrO   X   sg     't];F 	EUO	8D6!QRS]F1>FmR(.r    c                     t               }|j                  di       }| |vry|| = |s|j                  dd       t        |       y)z>Remove a server from config.yaml.  Returns True if it existed.rA   FNT)r	   rB   popr
   )rG   r?   rE   s      r   _remove_mcp_serverrR   k   sG    ]Fjj+G7

=$'r    rE   c                 <   g }| j                         D ]F  \  }}t        |t              s|j                  d| d       ,|j	                  t        ||             H |rd|fS t               }| rt        |       |d<   n|j                  dd       t        |       dg fS )u  Replace the WHOLE ``mcp_servers`` map in config.yaml.

    Unlike ``_save_mcp_server`` (per-key upsert), this sets the entire map so
    the GUI's mcp.json editor can delete servers, drop an ``enabled: false``
    flag (re-enable), or remove nested fields and have those *removals* land on
    disk.  A plain ``/api/config`` deep-merge can only add/override keys, never
    delete them — which is why edits appeared to succeed but the old entry
    survived (see MCP tab persistence bug).

    Every entry is validated up front; on any suspicious command/args the whole
    save is rejected (returns ``(False, issues)``) so a bad paste can't be
    partially applied.  An empty map removes the key entirely.
    rJ   z': expected an objectFrA   NT)	itemsrC   rD   appendextendr   r	   rQ   r
   )rE   rM   rG   cfgr?   s        r   _replace_mcp_serversrX   x   s     F]]_ <	c#t$MMHTF*?@A/c:;	< f}]F $W}

=$'8Or    c                 v    t        j                  dd| j                               j                  d      }d| dS )zDConvert server name to an env-var key like ``MCP_MYSERVER_API_KEY``.z[^A-Za-z0-9_]_MCP__API_KEY)resubupperr1   )rG   suffixs     r   _env_key_for_serverra      s5    VV$c4::<8>>sCF&""r    tokenc                     t        | t              s| S | j                         }|dd j                         dk(  r|dd j                         S |S )u#  Strip a leading ``Bearer `` from a pasted token.

    The header template stores ``Authorization: Bearer ${MCP_X_API_KEY}``, so
    if a user pastes a token that already includes the ``Bearer `` prefix the
    server receives ``Bearer Bearer <jwt>`` → 401. Normalize on save. (#37792)
    N   zbearer )rC   strr1   r2   )rb   strippeds     r   _strip_bearer_prefixrg      sO     eS!{{}H|y(|!!##Or    c                 (    t        |       }dd| diS )a  Build the persisted Authorization header for a named MCP server.

    The secret itself lives in the active profile's ``.env`` file. Keeping
    this template construction beside ``_env_key_for_server`` ensures the CLI
    and Dashboard produce byte-equivalent MCP configuration.
    Authorizationz	Bearer ${})ra   )rG   env_keys     r   _bearer_auth_headersrl      s"     "$'Gz'"566r    c                     t        |      }|r|j                         dk(  rt        d      t        t	        |       |       t        |       S )zPersist a Bearer token in the active profile and return safe headers.

    ``token`` is a one-time provisioning value. It is normalized and written
    only to ``.env``; callers persist the returned interpolation template in
    ``config.yaml``.
    bearerzBearer token is required)rg   r2   
ValueErrorr   ra   rl   )rG   rb   
normalizeds      r   _save_bearer_auth_tokenrq      sH     &e,J))+x7344&t,j9%%r    raw_envc                 D   i }| xs g D ]  }t        |xs d      j                         }|s#d|vrt        d| d      |j                  dd      \  }}|j                         }|st        d| d      t        j                  |      st        d| d      |||<    |S )	z;Parse ``KEY=VALUE`` strings from CLI args into an env dict.r8   =zInvalid --env value 'z' (expected KEY=VALUE)   z' (missing variable name)zInvalid --env variable name '')re   r1   ro   split_ENV_VAR_NAME_REmatch)rr   parseditemr   keyvalues         r   _parse_env_assignmentsr~      s    F2 4:2$$&d?4TF:PQRRZZQ'
Uiik4TF:STUU%%c*<SECDDs Mr    preset_nameurlr   cmd_argsc                &   |s|||dfS t         j                  |      }|st        d|       |s|r|||dfS |j                  d      }|j                  d      }t        |j                  d      xs g       }|r||d<   |r||d<   |r||d<   |||dfS )z=Apply a known MCP preset when transport details were omitted.FzUnknown MCP preset: r   r   r   T)r   rB   ro   list)rG   r   r   r   r   rH   presets          r   _apply_mcp_presetr      s     GXu,,k*F/}=>>
gGXu,,
**U
Cjj#GFJJv&,"-H
"e#*i  (f4''r    c                 r    ddl m} ddlm}  |       	 ddlm}  |         ||       S # t        $ r Y w xY w)u,  Resolve ``${ENV}`` placeholders in a server config before connecting.

    Mirrors ``_load_mcp_config()`` in ``tools/mcp_tool.py``: load
    ``~/.hermes/.env`` into ``os.environ`` and recursively interpolate any
    ``${VAR}`` placeholders. The CLI builds header templates like
    ``Authorization: Bearer ${MCP_X_API_KEY}`` but the probe path never
    resolved them, so the discovery probe sent the literal placeholder and
    auth-requiring servers (e.g. n8n) returned 401 — while runtime tool
    loading worked because it interpolates. (#37792)
    r   )_interpolate_env_vars)current_secret_scope)load_hermes_dotenv)tools.mcp_toolr   agent.secret_scoper   hermes_cli.env_loaderr   	Exception)r?   r   r   r   s       r   _resolve_mcp_server_configr      sB     57%	@  !((  		s   * 	66)detailsconnect_timeoutr   c                    t               }|rt        dj                  |            ddlm}m}mm}m t              (j                  dd      }	 t        dt        |             |        g  fd	}		  | |	       d
z          	  |        S # t        t        f$ r dY Dw xY w# t        $ r}
t        |
      dd}
~
ww xY w#  |        w xY w)ua  Temporarily connect to one MCP server, list its tools, disconnect.

    Returns list of ``(tool_name, description)`` tuples.
    Raises on connection failure.

    ``details``: optional dict the probe fills with extra capability counts
    (``prompts``, ``resources``) — an out-param so the return shape stays
    stable for existing CLI callers.
    z; r   )_ensure_mcp_loop_run_on_mcp_loop_connect_server_stop_mcp_loop_if_idle_parse_boolishNr      g      ?g      >@c                    K   t        j                   	             d {   } 	 | j                  D ]F  }t        |dd      xs d}t	        |      dkD  r|d d dz   }j                  |j                  |f       H j                  d      xs i } 
|j                  d      d	
      } 
|j                  d      d	
      }t        t        | dd       dd       dt        dt        ffd}|rC |d      r;	 | j                  j                          d {   }t	        |j                        d<   |rC |d      r;	 | j                  j                          d {   }t	        |j                        d<   | j!                          d {    y 7 q7 }# t        $ r Y nw xY w7 I# t        $ r Y :w xY w7 *# | j!                          d {  7   w xY ww)Ntimeoutdescriptionr8   P   M   ...toolspromptsTr+   	resourcesinitialize_resultcapabilitiescap_attrr,   c                 (    yt        | d       d uS )NT)getattr)r   advertised_capss    r   _advertisesz9_probe_single_server.<locals>._probe.<locals>._advertises[  s"     '.#"?HdC4OOr    )asynciowait_for_toolsr   lenrU   rG   rB   re   boolsessionlist_promptsr   r   list_resourcesr   shutdown)servertdesctools_filterprompts_enabledresources_enabledr   resultr   r   r   r?   r   r   rG   tools_founds           @r   _probez$_probe_single_server.<locals>._probe9  s    ''D&)?
 
4	$]] 3q-4:t9r>9u,D""AFFD>23 "  &zz'28b"0 $$Y/# %3 $$[14%! #*F$7>"#P# P$ P #{9'='-~~'B'B'D!D-0-@	* %[)A'-~~'D'D'F!F/263C3C/D, //###o
X "E$  "G$  $&//###s   "GFGCF> ?F FF 9
F> F- !F+"F- >GF<GF 	F(%F> 'F((F> +F- -	F96F> 8F99F> <G>GGGG
   r   )r   ro   joinr   r   r   r   r   r   r   rB   maxfloat	TypeErrorBaseException_unwrap_exception_group)rG   r?   r   r   rM   r   r   r   raw_timeoutr   excr   r   r   s   ````       @@@r   _probe_single_serverr     s     'tV4F6*++  (/Fjj!2B7	#!#u['9:O )+K8$ 8$t!?R+?@ 	 O :& 	#"O	#D  5%c*45 	 s6   B$ B; $B87B8;	CCCC 	C!c                     	 ddl m}  ||       j                         S # t        $ r!}t        j                  d| |       Y d}~yd}~ww xY w)a  Return True if an OAuth token file exists on disk for ``name``.

    Used after ``hermes mcp login`` to distinguish a genuine authentication
    from a probe that succeeded only because the server allowed
    initialize/tools-list without auth (so no token was ever acquired).
    r   )HermesTokenStoragez)Could not check OAuth tokens for '%s': %sNT)tools.mcp_oauthr   has_cached_tokensr   loggerdebug)rG   r   r   s      r   _oauth_tokens_presentr   }  sB    6!$'99;; @$Ls    	AAAr   c                     t        | t              r8| j                  r,| j                  d   } t        | t              r| j                  r,t        | t              r| S t	        t        |             S )aB  Extract the root-cause exception from anyio TaskGroup wrappers.

    The MCP SDK uses anyio task groups, which wrap errors in
    ``BaseExceptionGroup`` / ``ExceptionGroup``.  This makes error
    messages opaque ("unhandled errors in a TaskGroup").  We unwrap
    to surface the real cause (e.g. "401 Unauthorized").
    r   )rC   BaseExceptionGroup
exceptionsr   RuntimeErrorre   )r   s    r   r   r     sV     S,
-#..nnQ S,
-#.. #y!
C!!r    c                    | j                   }t        | dd      }t        | dd      }t        | dd      xs g }|r|d   dk(  r|dd }t        | dd      }t        | d	d      }t        | d
d      }t        | dd      }i }		 t        |      }
t        ||||t	        |      |	      \  }}}}|r|
rt        d       y|s:|s8t        d       t        d       t        d       t        d       t        d       yt               }||v rt        d| dd      st        d       y|r||	d<   n||	d<   |r||	d<   |
r|
|	d
<   |||	d<   t        ||	      }|r"|D ]  }t        |        t        d| d       y|r|dk(  rt                t        d| d       d}	 ddlm}  |       j!                  ||d      }|rd|	d<   t#        d       d}nt        d        |st        d"       t        d#d      rnt        d       y|rt                t        d$|        t        d%d      }|ru|d&k(  s|snt'        |      }t)        |      }|rt#        | d'       n7t+        d(d)      }|r(t-        ||      |	d*<   t#        d+t/                d,|        |rt1        |      |	d*<   t                t        t3        d-| dt4        j6                               	 t9        ||	      }|s4t        d4       t        d5d      rt;        ||	      rt#        d1| d6       yt                t#        d7t=        |       d8| d9       t                |D ]G  \  }}t=        |      d:kD  r|dd: d;z   n|}t        d<t3        |t4        j>                        d=d>|        I t                	 tA        t3        d?t=        |       d@t4        jB                              jE                         jG                         }|dAv rt        dB       y|dCv rddDl&m'} |D cg c]  }|d    dE|d     }}tQ        tS        t=        |                  } |dF| dG||      } | st        dH       ytU        |       D !cg c]
  }!||!   d    }"}!|"|	jW                  dIi       dJ<   t=        |"      }#t=        |      }$nt=        |      }#t=        |      }$d|	d0<   t;        ||	      r6t                t#        d1| dKt/                dL|# dM|$ dN	       t        dO       yy# t
        $ r}t        t        |             Y d}~yd}~ww xY w# t$        $ r}t        d!|        Y d}~gd}~ww xY w# t$        $ rS}t        d.|        t        d/d      r.d|	d0<   t;        ||	      rt#        d1| d2       t        d3|z          Y d}~yd}~ww xY w# tH        tJ        f$ r t                t        d       Y yw xY wc c}w c c}!w )Pz9Add a new MCP server with discovery-first tool selection.r   Nmcp_commandr   r   z--ru   authr   envr   )r   r   r   r   rH   zJ--env is only supported for stdio MCP servers (--command or stdio presets)zBMust specify --url <endpoint>, --command <cmd>, or --preset <name>z	Examples:z3  hermes mcp add ink --url "https://mcp.ml.ink/mcp"zP  hermes mcp add github --command npx --args @modelcontextprotocol/server-githubz+  hermes mcp add myserver --preset mypresetrJ   z' already exists. Overwrite?Fr   
Cancelled.r   rK   oauthStarting OAuth flow for ''...get_managerz>OAuth configured (tokens will be acquired on first connection)Tu8   OAuth setup failed — MCP SDK auth module not availablezOAuth error: z"This server may not support OAuth.z Continue without authentication?zConnecting to z(Does this server require authentication?headerz: already configuredzAPI key / Bearer token)r9   headersz	Saved to z	/.env as   Connecting to 'Failed to connect: z(Save config anyway (you can test later)?enabledzSaved 'z' to config (disabled)z%Fix the issue, then: hermes mcp test z'Server connected but reported no tools.zSave config anyway?z' to configzConnected! Found z tool(s) from 'z':<   r       40s z  Enable all z tools? [Y/n/select]: >   nnou   Cancelled — server not saved.>   sselectcurses_checklist     —  Select tools for 'rv   u'   No tools selected — server not saved.r   includez' to z/config.yaml (/z tools enabled)z'Start a new session to use these tools.),rG   r   r~   r   r   ro   r)   re   r   rF   r7   r   r&   r   tools.mcp_oauth_managerr   get_or_build_providerr#   r   ra   r   r>   rq   r   rl   r   r   CYANr   rO   r   r"   r0   r%   r1   r2   r3   r4   hermes_cli.curses_uir   setrangesortedrL   )%r   rG   r   r   r   	auth_typer   rr   raw_connect_timeoutrH   explicit_env_preset_appliedr   existingrM   rN   oauth_okr   
oauth_auth
needs_authrk   existing_keyapi_keyr   	tool_namer   shortchoicer   r   labelspre_selectedchosenichosen_names
tool_counttotals%                                        r   cmd_mcp_addr	    s=   99D
$t
$C dM40GtVT*0bHHQK4'AB<fd+I$$/KdE4(G!$(94@$&M-g62C#(^'3
/Wh |[\ wSTkCD`a;<  !Hx(4&(DEuU, "e#*i $,M&!#/M% &+>'(&t];F 	EUO	8D6!QRS yG#)$t45
	,;$<<T3MJ(/f%YZST 67:DIl#	se$%HRVW
H$I-d3,W5y(<=>%&>NG3J '4i0 !9-@-B,C9WI!VW  /CD/IM), 
G	%#D6.
<=	$T=9 :;)48m474&45 
G UOD6DE	G  C	4%(Y^Sb	E!U9fll3C8%ABC 
GM#e*-CDfmmT

%'%%' 	 /0 9278QQqTF'!A$(885U,-! a(
 ;<-3F^<a<<;G  "-i8&
E
 Z
E
  $M)m,74&&9&;%<N:,VWX]W^^mno78 -U  s3xt  	,}SE*++	,T  $SE*+>N',M)$m474&(>?@=DE> x( l 9 =sh   >*S2 >T 	U AV  ;W
W2	T;TT	T>%T99T>	V
A	VV $WWc                    | j                   }t               }||vrHt        d| d       t        |j	                               }|rt        ddj                  |              yt        d| dd	      st        d
       yt        |       t        d| d       	 ddl
m}  |       j                  |       t        d       y# t        $ r Y yw xY w)z!Remove an MCP server from config.rJ   ' not found in config.Available servers: , NzRemove server 'z'?Tr   r   z	Removed 'z' from configr   r   zCleaned up OAuth tokens)rG   rF   r)   r   keysr   r   r7   rR   r#   r   r   remover   )r   rG   r   rE   r   s        r   cmd_mcp_remover  l  s    99D!H8$567x}}''		'(:';<=odV2.=ltym,-
7T"*+ s   'C 	CCc                 $   t               }|sKt                t        d       t                t        d       t        d       t        d       t                yt                t        t        dt        j
                  t        j                  z                t                t        ddd	d
ddd
ddd
dd       t        dd d
d d
d d
d        |j                         D ]  \  }}d|v r|d   }t        |      dkD  r|dd dz   }|}nid|v rc|d   }|j                  dg       }t        |t              r#|r!| d
d
j                  d |dd D               }n|}t        |      dkD  r|dd dz   }nd}|j                  di       }t        |t              rh|j                  d      }	|j                  d       }
|	rt        |	t              rt        |	       d!}n'|
r t        |
t              rd"t        |
       d#}nd$}nd$}|j                  d%d&      }t        |t              r|j                         d'v }|rt        d(t        j                         nt        d)t        j"                        }t        d|d	d
|dd
|dd
|         t                y)*z List all configured MCP servers.zNo MCP servers configured.zAdd one with:z(  hermes mcp add <name> --url <endpoint>z8  hermes mcp add <name> --command <cmd> --args <args...>Nz  MCP Servers:r   Namez<16r   	Transportz<30Toolsz<12Statusz<10u0   ────────────────uZ   ──────────────────────────────u$   ────────────u   ──────────r         r   r   r   c              3   2   K   | ]  }t        |        y wN)re   ).0as     r   	<genexpr>zcmd_mcp_list.<locals>.<genexpr>  s     -Kc!f-Ks      ?r   r   excludez	 selected-z	 excludedallr   T>   1r/   trueu   ✓ enabledu   ✗ disabled)rF   r   r   r   r   r   BOLDrT   r   rB   rC   r   r   rD   re   r2   r"   r   )r   rE   rG   rW   r   	transportcmdr   	tools_cfgr   r  	tools_strr   statuss                 r   cmd_mcp_listr*    su    G*+o89HI	G	% &++";
<=	G 
Bvcl!K,Agc]!HS>
JK	Bzl!J<qAj\
BC]]_ (H	cC<e*C3x"}#2h&I#i.Cwwvr*H(D)h"e1SXX-Khrl-K%K$LM		9~"%crNU2	I GGGR(	i&mmI.GmmI.G:gt4"7|nI6	Z6G~Y7	!	I '')T*gs#mmo)==G7>}fll3E.Z`ZdZdDe4*Ai_Ai_AfXFGQ(HT 
Gr    c                 \   | j                   }t               }||vrHt        d| d       t        |j	                               }|rt        ddj                  |              y||   }t                t        t        d| dt        j                               d|v rt        d	|d           n |j                  d
d      }t        d|        |j                  dd      }|j                  di       }|dk(  rt        d       n|r|j                         D ]  \  }}	t        |	t              sd|j                         v sd|j                         v s<t!        j"                  d |	      }
t%        |
      dkD  r|
dd dz   |
dd z   }nd}t        d| d|         nt        d       t'        j(                         }	 t+        ||      }t'        j(                         |z
  dz  }t/        d|dd        t/        d!t%        |              |rVt                |D ]G  \  }}t%        |      d"kD  r|dd" d#z   n|}t        dt        |t        j0                        d$d%|        I t                y# t,        $ r6}t'        j(                         |z
  dz  }t        d|dd|        Y d}~yd}~ww xY w)&z!Test connection to an MCP server.rJ   r  Available: r  Nz  Testing 'r   r   u   Transport: HTTP → r   r  u   Transport: stdio → r   r8   r   r   zAuth: OAuth 2.1 PKCEr|   c                 ^    t        j                  t        | j                  d            d      S )Nru   r8   )osgetenvr   group)ms    r   <lambda>zcmd_mcp_test.<locals>.<lambda>  s$    "))MRSRYRYZ[R\D]_a:b r          z***r   z: z
Auth: nonei  zConnection failed (z.0fzms): zConnected (zms)zTools discovered: 7   r   36sr   )rG   rF   r)   r   r  r   r   r   r   r   r   rB   rT   rC   re   r2   r   r^   r   time	monotonicr   r   r#   r"   )r   rG   rE   	availablerW   r&  r   r   kvresolvedmaskedstartr   
elapsed_msr   r   r   r   s                      r   cmd_mcp_testrA    s   99D G7$567(	K		) 4567
$-C	G	%+dV4(&++
67 |$SZL12ggi%%cU+, #Iggi$GG$%	MMO 	,DAq!S!u	'9Vqwwy=P+//0bdefx=1$%bq\E1HRSMAF"FQCr&*+	, 	l NNE$T3/nn&.$6
 {:c*#./!#e*./$ 	GOIt),TRD"I%TEDy&,,7<AeWEF	G 
G  nn&.$6
$Z$4E#?@s   <&I, ,	J+5,J&&J+c                 R   |j                  d      }|st        d|  d       y|j                  d      dk7  r-t        d|  d|j                  d       d       t        d	       y	 d
dlm}  |       j                  |        t                t        d|  d       	 |j                  d      }	 t        |      }t        |d      }t        | ||      }t        |       sBt        d       t                t        d       t                t        t        dt         j"                               t        t        d|  dt         j"                               t        t        d| t         j"                               t        t        dt         j"                               t        t        dt         j"                               t        t        dt         j"                               t        t        dt         j"                               t                t        d| z   dz          y|rt%        d t'        |       d!       y#t%        d"       y## t        $ r}t        d|        Y d}~d}~ww xY w# t        t        f$ r d}Y w xY w# t        $ r}t        d$|        Y d}~yd}~ww xY w)%aj  Force a fresh OAuth flow for one server. Returns True on success.

    Wipes cached OAuth state (disk + in-process MCPOAuthManager cache),
    re-probes to trigger the browser flow, and verifies a token actually
    landed before reporting success. Shared by ``hermes mcp login`` and
    ``hermes mcp reauth`` so both behave identically for a single server.
    r   rJ   u,   ' has no URL — not an OAuth-capable serverFr   r   z$' is not configured for OAuth (auth=)z?Use `hermes mcp remove` + `hermes mcp add` to reconfigure auth.r   r   z&Could not clear existing OAuth state: Nr   r   r   g        g     s@)r   uV   Server responded, but no OAuth token was obtained — authentication did not complete.zSome providers (e.g. Google Drive, Atlassian) do not support automatic client registration. For those you must create an OAuth client yourself and add its credentials to config.yaml:z    mcp_servers:z      :z        url: z        auth: oauthz        oauth:z-          client_id: "<your-oauth-client-id>"z5          client_secret: "<your-oauth-client-secret>"zThen re-run `hermes mcp login z`.u   Authenticated — z tool(s) availablez(Authenticated (server reported no tools)TzAuthentication failed: )rB   r)   r   r   r   r  r   r&   r   r   r   ro   r   r   r   r   r   r   r#   r   )rG   rH   r   r   r   _login_connect_timeoutr   s          r   _reauth_oauth_serverrF    sm    

E
"C$KLM G+$CMDUDUV\D]C^^_`aOPA7T" 
G	%dV4
01/!.!2!23D!E	)%*+A%B" "%%;U!C$-1G
 %T*3 GP
 G%*FJJ78%&a(&**56%-u-vzz:;%-vzz:;%(&**56%I6::VW%QSYS]S]^_G2T9D@A)#e*5GHI  ?@q  A9#?@@A  :& 	)%("	)R  (./sa   &I J .I- 9E'J !J <J 	I*I%%I*-J>J JJ 	J&J!!J&c                     | j                   }t               }||vr/t        d| d       |rt        ddj	                  |              yt        |||          y)a  Force re-authentication for an OAuth-based MCP server.

    Deletes cached tokens (both on disk and in the running process's
    MCPOAuthManager cache) and triggers a fresh OAuth flow via the
    existing probe path.

    Use this when:
      - Tokens are stuck in a bad state (server revoked, refresh token
        consumed by an external process, etc.)
      - You want to re-authenticate to change scopes or account
      - A tool call returned ``needs_reauth: true``
    rJ   r  r  r  N)rG   rF   r)   r   r   rF  )r   rG   rE   s      r   cmd_mcp_loginrH  e  s]     99D G7$567'		'(:';<=wt}-r    c                    t               }t        | dd      }t        | dd      }|r|j                         D cg c].  \  }}|j                  d      dk(  r|j                  d      r||f0 }}}|st	        d       yt                t	        d	t        |       d
       d}|D ]Y  \  }}t                t        t        d| dt        j                  t        j                  z                t        ||      sU|dz  }[ t                t        d| dt        |       d       y|st        d       t	        d       y||vr/t        d| d       |rt	        ddj                  |              yt        |||          yc c}}w )ad  Re-authenticate one OAuth MCP server, or all of them sequentially.

    ``hermes mcp reauth <name>`` re-auths a single server (same as ``login``).
    ``hermes mcp reauth --all`` discovers every ``auth: oauth`` server in
    config and re-auths them ONE AT A TIME.

    Serial-by-design: a human can only complete one browser OAuth flow at a
    time, so re-authing all servers concurrently would open N tabs at once
    and N-1 would time out. This is the self-service fix for the recurring
    stale-client ritual in GH#36767 (and avoids the startup popup storm when
    several servers go stale at once).
    r!  FrG   Nr   r   r   z+No OAuth-based MCP servers found in config.zRe-authenticating z! OAuth server(s) one at a time...r   u	     ── u    ──ru   zRe-authenticated r   z
 server(s)zBSpecify a server name, or use --all to re-auth every OAuth server.z=Usage: hermes mcp reauth <name>   |   hermes mcp reauth --allrJ   r  r  r  )rF   r   rT   rB   r   r   r   r   r   r   r$  rF  r#   r)   r   )r   rE   do_allrG   r   coauth_servers	succeededs           r   cmd_mcp_reauthrN  ~  s     GT5%(F4&D&}}
q!uuV}'AEE%L F
 
 ?@"3}#5"66WXY	! 	DAqG%)A3g.fkk0IJK#Aq)Q			
 	$YKq]1C0DJOPSTMN7$567'		'(:';<=wt}-;
s   3Fc           	         ddl }|j                  j                         s(t        d|j                         |j                  d       | j                  }t               }||vrHt        d| d       t        |j                               }|rt        dd	j                  |              y||   }t                t        t        d
| dt        j                               	 t!        ||      }|st%        d       y|j'                  di       }t)        |t*              r#|j'                  d      }	|j'                  d      }
nd}	d}
|D cg c]  }|d   	 }}|	r>t)        |	t              r.t-        |	      }t/        |      D ch c]  \  }}||v s| }}}n]|
r>t)        |
t              r.t-        |
      }t/        |      D ch c]  \  }}||vs| }}}nt-        t1        t3        |                  }t3        |      }t3        |      }t        d| d| d| d       t                ddlm} |D cg c]  }|d    d|d     }} |d| d||      }||k(  rt        d       yt9               }t;        |d|i       }t3        |      |k(  r|j=                  dd       nJt?        |      D cg c]  }||   	 }}|jA                  di        ||d   d<   |d   j=                  dd       ||jA                  di       |<   tC        |       t3        |      }tE        d| d| d       t        d       y# t"        $ r}t        d|        Y d}~yd}~ww xY wc c}w c c}}w c c}}w c c}w c c}w )z?Reconfigure which tools are enabled for an existing MCP server.r   Nz?Error: 'hermes mcp configure' requires an interactive terminal.)fileru   rJ   r  r,  r  r   z' to discover tools...r   zServer reports no tools.r   r   r  z
Currently r   z tools enabled for 'z'.r   r   r   rv   zNo changes made.rA   r   zUpdated config: z tools enabledz/Start a new session for changes to take effect.)#sysstdinisattyr   stderrexitrG   rF   r)   r   r  r   r   r   r   r   r   r   r&   rB   rC   rD   r   	enumerater   r   r   r   r	   r   rQ   r   rL   r
   r#   )r   _sysrG   rE   r:  rW   	all_toolsr   r'  r   r  r   
tool_namesinclude_setr  tnr  exclude_set	currentlyr  r   r  r  r?   server_entryr  	new_counts                              r   cmd_mcp_configurer`    sh   ::OVZVaVab		!99D G7$567(	K		) 4567
$-C 
G	%#D6)?@&++
NO(s3	
 +, $I)T"--	*--	* )*1!A$*J*:gt,'l$Z0
!RB+4EA
 
 
Z.'l$Z0
!RBk4IA
 
 5Y01L!I	NE	Jyk5')=dV2
FG	G 6.781gadV$8F8
TF!$F  ! ]F6=$CL
6{e$'/5f~>!
1>>,+7Wi(W!!)T21=FmR(.FI	{!E7.AB	
;<M  $SE*+" +


 9( ?sB    L3 M MM M"M"1M(-M-3	M<MMc                 R   t        | dd      }|dk(  rddlm}  |t        | dd             y|d	k(  rdd
lm}  |        y|dk(  rddlm}  |        y|dk(  r5ddlm} ddl} |t        | dd      xs d      }|r|j                  |       yt        t        t        t        t        t        t        t        t        t        d
}|j!                  |      }	|	r	 |	|        ydd
lm}  |        t#        t%        dt&        j(                               t+        d       t+        d       t+        d       t+        d       t+        d       t+        d       t+        d       t+        d       t+        d       t+        d       t+        d       t+        d       t+        d       t#                y) z/Main dispatcher for ``hermes mcp`` subcommands.
mcp_actionNserver   )run_mcp_serververboseF)re  picker)
run_pickercatalog)show_cataloginstall)install_by_name
identifierr8   )
addr  rmr   lstest	configurer?   loginreauthz  Commands:zOhermes mcp                                    Open the catalog picker (default)zEhermes mcp catalog                            List Nous-approved MCPszChermes mcp install <name>                     Install a catalog MCPz?hermes mcp serve                              Run as MCP serverzEhermes mcp add <name> --url <endpoint>        Add a custom MCP serverz@hermes mcp add <name> --command <cmd>         Add a stdio serverzEhermes mcp add <name> --preset <preset>       Add from a known presetz=hermes mcp remove <name>                      Remove a serverzEhermes mcp list                               List configured serversz=hermes mcp test <name>                        Test connectionz:hermes mcp configure <name>                   Toggle toolszChermes mcp login <name>                       Re-authenticate OAuthzNhermes mcp reauth <name> | --all              Re-auth one or all OAuth servers)r   	mcp_serverd  hermes_cli.mcp_pickerrg  ri  rk  rQ  rU  r	  r  r*  rA  r`  rH  rN  rB   r   r   r   r   r   )
r   actionrd  rg  ri  rk  rW  rchandlershandlers
             r   r   r     sf   T<.F,wtY>? 469WT<<BCIIbM  &# H ll6"G 	5eM6;;/0_`UVSTOPUVPQUVMNUVMNJKST^_r    )Tr  )H__doc__r   loggingr.  r]   r8  typingr   r   r   r   r   hermes_cli.configr   r	   r
   r   r   r   hermes_cli.colorsr   r   hermes_constantsr   hermes_cli.mcp_securityr   r   r   r   	getLogger__name__r   compilerx   r   re   __annotations__r   r#   r&   r)   r   r7   r>   rD   rF   rO   rR   rX   ra   rg   rl   rq   r~   tupler   r   r   r   r   r   r   r   r	  r  r*  rA  rF  rH  rN  r`  r    r    r   <module>r     sb     	 	  3 3  , 0 = :			8	$2::9:  +d3S#X&' * *03 013 1. .	s 	T 	T 	 05R Hc H Hs HC HXd^ tCI 3 t  &
S 
T 
$sDy/ eD$s)O6L B#c #c #  7s 7tCH~ 7&# &c &d38n &HT#Y$7 DcN &(
( #( 
#	(
 c]( 3i( S>( 8C=(3-cD89(H)t ) )2 AEdcgd
dd.6uodRZ[_R`d	%S/dN   " "9 "$H9Z@@J;@Qs Q4 QD Qh.2/.h^=F>r    