
    kKj.              	          d Z ddlZddlZddlZddlmZ ddlmZ ddlm	Z	 ddl
Z
ddlmZ ddlmZmZmZmZ ddlmZ  ed	      Z ed
       G d d             Zdeee	f   deee	f   fdZdeee	f   dededee   fdZdededee   fdZdee   fdZdedee   fdZdedee   fdZdedee   fdZdedee   fdZ dee   fdZ!d!dedz  dee   fdZ"d!dededz  defd Z#y)"a\  Discover MCP servers configured in editor config files.

Scans filesystem-readable config files from editors like Claude Desktop,
Claude Code, Cursor, Gemini CLI, and Goose, as well as project-level
``mcp.json`` files. Each discovered server can be resolved by name
(or ``source:name``) so the CLI can connect without requiring a URL
or file path.
    N)	dataclass)Path)Any)ClientTransport)	MCPConfigMCPServerTypesRemoteMCPServerStdioMCPServer)
get_loggerzcli.discoveryT)frozenc                   f    e Zd ZU dZeed<   eed<   eed<   eed<   edefd       Z	edefd       Z
y	)
DiscoveredServerz9A single MCP server found in an editor or project config.namesourceconfigconfig_pathreturnc                 8    | j                    d| j                   S )z+Fully qualified ``source:name`` identifier.:)r   r   )selfs    f/Users/ahmed/devFolder/Ultron/claude-voice/.venv/lib/python3.12/site-packages/fastmcp/cli/discovery.pyqualified_namezDiscoveredServer.qualified_name-   s     ++a		{++    c                 2   | j                   }t        |t              r-|j                  g|j                  }ddj                  |       S t        |t              r!|j                  xs d}| d|j                   S t        t        |      j                        S )z2Human-readable one-liner describing the transport.zstdio:  httpz: )r   
isinstancer
   commandargsjoinr	   	transporturlstrtype__name__)r   cfgpartsr!   s       r   transport_summaryz"DiscoveredServer.transport_summary2   s     kkc>*[[,388,ESXXe_-..c?+/I[377),,49%%&&r   N)r%   
__module____qualname____doc__r#   __annotations__r   r   propertyr   r(    r   r   r   r   $   sO    C
IK, , , 	'3 	' 	'r   r   entryr   c                     d| v rd| vri | d| d   i} | d= d| v r;d| v r7d| vr3| d   }| j                         D ci c]  \  }}|dk7  s|| } }}|| d<   | S c c}}w )a	  Normalize editor-specific server config fields to MCPConfig format.

    Handles two known differences:
    - Claude Code uses ``type`` where MCPConfig uses ``transport`` for
      remote servers.
    - Gemini CLI uses ``httpUrl`` where MCPConfig uses ``url``.
    httpUrlr"   r$   r!   )items)r/   r!   kvs       r   _normalize_server_entryr5   D   s     Ee50252%y!12) ~&E/k.F&M	"'++-?-$!Q1;A-?&kL @s   A	Aservers_dictr   r   c          
         | sg S | j                         D ci c]!  \  }}t        |t              r|t        |      # }}}	 t	        j
                  d|i      }|j                  j                         D cg c]  \  }}t        ||||       c}}S c c}}w # t        $ r#}t        j                  d||       g cY d}~S d}~ww xY wc c}}w )z;Parse an ``mcpServers``-style dict into discovered servers.
mcpServersz'Could not parse MCP servers from %s: %sN)r   r   r   r   )r2   r   dictr5   r   	from_dict	Exceptionloggerwarningr8   r   )	r6   r   r   r   r/   
normalizedr   excservers	            r   _parse_mcp_serversrA   Z   s     	 (--//KD%eT" 	%e,,/  $$lJ%?@ #--335	 6LD& 	fV	
 6	   @+sS	s)   &BB 6C	C"C :C Cpathc                 n   	 | j                         }	 t	        j
                  |      }t        |t              rd|vrg S t        |d   ||       S # t        $ r#}t        j                  d| |       g cY d}~S d}~ww xY w# t        j                  $ r#}t        j                  d| |       g cY d}~S d}~ww xY w)z<Parse an mcpServers-style JSON file into discovered servers.zCould not read %s: %sNInvalid JSON in %s: %sr8   r   r   )	read_textOSErrorr<   debugjsonloadsJSONDecodeErrorr=   r   r9   rA   )rB   r   textr?   datas        r   _parse_mcp_configrN   x   s    ~~
#zz$/
 dD!\%=	d<0TRR  ,dC8	  /s;	s:   A A> 	A;A60A;6A;>B4B/)B4/B4c                     t         j                  dk(  r!t        t        j                         ddd      } nt         j                  dk(  r!t        t        j                         ddd      } nat         j                  j	                  d      r@t        t
        j                  j                  d	t        j                         d
z        d      } ng S | dz  }t        |d      S )z$Scan the Claude Desktop config file.win32AppDataRoamingClaudedarwinLibraryzApplication SupportlinuxXDG_CONFIG_HOME.configzclaude_desktop_config.jsonzclaude-desktop)	sysplatformr   home
startswithosenvirongetrN   )
config_dirrB   s     r   _scan_claude_desktopra      s    
||w$))+y)XF
		!$))+y2GR
		 	 	)JJNN,diikI.EF

 	44DT#344r   	start_dirc                    t        j                         dz  }	 |j                         }	 t	        j
                  |      }t        |t              sg S g }|j                  d      x}r-t        |t              r|j                  t        |d|             t        | j                               }|j                  di       }t        |t              rb|j                  |i       }	t        |	t              r@|	j                  d      x}
r-t        |
t              r|j                  t        |
d|             |S # t        $ r g cY S w xY w# t        j                  $ r#}t        j                  d||       g cY d}~S d}~ww xY w)zBScan ``~/.claude.json`` for global and project-scoped MCP servers.z.claude.jsonrD   Nr8   zclaude-coderE   projects)r   r[   rF   rG   rI   rJ   rK   r<   r=   r   r9   r_   extendrA   r#   resolve)rb   rB   rL   rM   r?   resultsglobal_serversresolved_dirrd   project_dataproject_serverss              r   _scan_claude_coderl      sX   99;'D~~#zz$/
 dD!	&(G ,//~/nd+NN""=d y((*+Lxx
B'H(D!||L"5lD)"."2"2<"@@@ot4NN*+#0(, NO  	
  /s;	s.   D2 E 2E ?E E9E4.E94E9c                     | j                         }t        j                         j                         }	 |dz  dz  }|j                         rt	        |d      S |j
                  }||k(  s||k(  r	 g S |}@)z:Walk up from *start_dir* looking for ``.cursor/mcp.json``.z.cursormcp.jsoncursor)rf   r   r[   is_filerN   parent)rb   currentr[   	candidaterq   s        r   _scan_cursor_workspacert      s|    !G99; D
i'*4	$Y99W4 I  r   c                 d    | j                         dz  }|j                         rt        |d      S g S )z&Check for ``mcp.json`` in *start_dir*.rn   project)rf   rp   rN   )rb   rs   s     r   _scan_project_mcp_jsonrw      s3    !!#j0I I66Ir   c                     g }t        j                         dz  dz  }|j                  t        |d             | j	                         dz  dz  }||k7  r|j                  t        |d             |S )zScan Gemini CLI settings for MCP servers.

    Checks both user-level ``~/.gemini/settings.json`` and project-level
    ``.gemini/settings.json``.
    z.geminizsettings.jsongemini)r   r[   re   rN   rf   )rb   rg   	user_pathproject_paths       r   _scan_geminir|      so     ')G 		i'/9INN$Y9: $$&2_DLy (x@ANr   c                     t         j                  dk(  rEt        t        j                  j                  dt        j                         dz  dz        ddd      } n?t        t        j                  j                  dt        j                         d	z        d      } | d
z  }	 |j                         }	 t        j                  |      }t        |t              sg S |j                  di       }t        |t              sg S i }|j!                         D ]  \  }}t        |t              s|j                  dd      s*|j                  dd      }	|	dk(  r1d|v r-|d   |j                  dg       |j                  di       d||<   r|	dk(  sxd|v s}|d   dd||<    t#        |d|      S # t        $ r g cY S w xY w# t        j                  $ r#}t        j                  d||       g cY d}~S d}~ww xY w)u   Scan Goose config for MCP server extensions.

    Goose uses YAML (``~/.config/goose/config.yaml``) with a different
    schema — MCP servers are defined as ``extensions`` with ``type: stdio``.
    rP   APPDATArQ   rR   Blockgooser   rW   rX   zconfig.yamlzInvalid YAML in %s: %sN
extensionsenabledTr$    stdiocmdr   envs)r   r   envsseuri)r"   r!   rE   )rY   rZ   r   r]   r^   r_   r[   rF   rG   yaml	safe_load	YAMLErrorr<   r=   r   r9   r2   rA   )
r`   rB   rL   rM   r?   r   serversr   extext_types
             r   _scan_gooser      s    ||wJJNN9diikI&=	&IJ	

 JJNN,diikI.EF


 %D~~~~d#
 dD!	,+Jj$'	 !G%%'	c#t$wwy$'7762&w5C<u:+wwvr*GDM
 5C<$'JUCGDM ( gg4HHC  	
 >> /s;	s0   F$ /F5 $F21F25G+G& G+&G+c                 n   | xs t        j                         }g }|j                  t                      |j                  t	        |             |j                  t        |             |j                  t        |             |j                  t                      |j                  t        |             |S )u   Run all scanners and return the combined results.

    Duplicate names across sources are preserved — callers can
    use :pyattr:`DiscoveredServer.qualified_name` to disambiguate.
    )	r   cwdre   ra   rl   rt   r|   r   rw   )rb   r   rg   s      r   discover_serversr   :  s     
!txxzC&(GNN')*NN$S)*NN)#./NN<$%NN;=!NN)#./Nr   r   c                    t        |      }d| v ru| j                  dd      \  }}|D cg c]$  }|j                  |k(  s|j                  |k(  s#|& }}|st	        d| d| d      |d   j
                  j                         S |D cg c]  }|j                  | k(  s| }}|si|rCdj                  t        |D ch c]  }|j                   c}            }t	        d|  d|       g d	}t	        d|  d
dj                  |             t        |      dk(  r|d   j
                  j                         S dj                  d |D              }	t	        d|  d|	       c c}w c c}w c c}w )zResolve a server name (or ``source:name``) to a transport.

    Raises :class:`ValueError` when the name is not found or is ambiguous.
    r      zNo server named 'z' found in source 'z'.r   z, z' found. Available: )zClaude Desktop configz~/.claude.json (Claude Code)z%.cursor/mcp.json (walked up from cwd)z$~/.gemini/settings.json (Gemini CLI)z#~/.config/goose/config.yaml (Goose)z
./mcp.jsonz' found. Searched: c              3   <   K   | ]  }d |j                    d   yw)'N)r   ).0ms     r   	<genexpr>zresolve_name.<locals>.<genexpr>s  s!     Fgq!1!1 2!4gs   zAmbiguous server name 'u7   ' — found in multiple sources. Use a qualified name: )
r   splitr   r   
ValueErrorr   to_transportr    sortedlen)
r   rb   r   r   server_namesmatches	available	locationsalternativess
             r   resolve_namer   K  s   
 y)G d{"jja0%VgV);+@U1gV#K=0CF82N  qz  --// "4'QQVVt^q'G4		&')B'Q!&&')B"CDI06J9+VWW
	 v%899M8NO
 	
 7|qqz  --// 99FgFFL

!$ (!!-	0 ? W 5 *Cs"   E#E#E#	E(E(<E-)N)$r+   rI   r]   rY   dataclassesr   pathlibr   typingr   r   fastmcp.client.transports.baser   fastmcp.mcp_configr   r   r	   r
   fastmcp.utilities.loggingr   r<   r   r9   r#   r5   listrA   rN   ra   rl   rt   rw   r|   r   r   r   r.   r   r   <module>r      s    	 
 !    :  1	O	$ $' ' '>4S> d38n ,sCx.  	
 

<SD S# S$7G2H S(5d#34 5", ,$/?*@ ,^d t4D/E &d t4D/E D T*:%; (7IT*+ 7I~t t<L7M ",s ,td{ ,o ,r   