
    	i'                       d Z ddlmZ ddlZddlZddlZddlmZmZm	Z	m
Z
 ddlZddl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  e         G d
 d      ZddZedk(  r ej8                   e              yy# e$ r Y 3w xY w)a  
Interactive LangChain + Ollama chatbot wired to the MCP recommender server.

Usage
-----
1. Start the MCP server in HTTP mode (default): `python mcp_server.py`
2. Ensure Ollama is running and the model in $OLLAMA_MODEL is pulled.
3. Optional environment variables:
       export MCP_SERVER_URL="http://127.0.0.1:7860"
       export MCP_TRANSPORT="http"           # set to "sse" for legacy transports
       export MCP_AUTH_KEY="<only for SSE servers>"
       export OLLAMA_MODEL="qwen3"
4. Run the chatbot:
       python tests/ollama_mcp_chatbot.py

The script discovers all MCP tools, wraps them as LangChain tools, and drops you
into a REPL so you can talk to the recommender through natural language.
    )annotationsN)AnyDictListOptional)AgentExecutorcreate_tool_calling_agent)ChatPromptTemplateMessagesPlaceholder)tool)
ChatOllama)load_dotenvc                      e Zd ZdZ	 	 d	 	 	 	 	 ddZddZddZedd       ZdddZ	ddZ
dd	Zdd
ZddZddZddZy)LocalMCPClientzGClient that can speak either HTTP (default) or the older SSE transport.Nc                   |xs dj                         }|dvrt        d      |dk(  r|j                  d      dz   }n|j                  d      }|| _        || _        || _        | j                         | _        d| _        d| _	        d | _
        i | _        i | _        g | _        t        j                  dd	      }t!        |d
      | _        d | _        g | _        y )Nhttp>   sser   z!transport must be 'http' or 'sse'r   /r   FOLLAMA_MODELqwen3)modeltemperature)lower
ValueErrorrstripbase_urlauth_key	transport
_build_urlurl
request_idinitializedhttp_clientserver_infocapabilities	mcp_toolsosgetenvr   llmagent_executortools)selfr   r   r   ollama_models        tests/ollama_mcp_chatbot.py__init__zLocalMCPClient.__init__,   s     (&//1	O+@AAs+c1Hs+H  "??$ 8<+-,./1yy9La@7;
    c                    | j                   dk7  r| j                  S | j                  r/d| j                  v rdnd}| j                   | d| j                   S | j                  S )Nr   ?&zauthKey=)r   r   r   )r,   	separators     r.   r   zLocalMCPClient._build_urlL   sX    >>U"== =="dmm3Imm_YKxGG}}r0   c                D    | xj                   dz  c_         | j                   S )N   )r!   r,   s    r.   _next_idzLocalMCPClient._next_idT   s    1r0   c                    | j                         j                  d      }|D cg c]&  }|j                  d      s|j                  dd      ( }}|rt	        j
                  |d         S yc c}w )z4Extract the JSON payload from an SSE response block.
zdata:  r   N)stripsplit
startswithreplacejsonloads)textlinesline
data_liness       r.   _parse_sse_responsez"LocalMCPClient._parse_sse_responseX   sd     

""4(=B`UTdooV^F_dll8R0U
`::jm,, as
   A,A,c                  K   | j                   st        d      d| j                         ||xs i d}ddd}| j                   j                  | j                  ||       d{   }|j                          | j                  |j                        }|t        d	|j                         d
|v r4|d
   }t        d|j                  d       d|j                  d             |j                  di       S 7 w)z-Send a JSON-RPC request over the SSE channel.zHTTP client not initializedz2.0)jsonrpcidmethodparamsz#application/json, text/event-streamzapplication/json)AcceptzContent-Type)r@   headersNzMalformed SSE payload: errorz
MCP error codez: messageresult)	r#   RuntimeErrorr8   postr    raise_for_statusrF   rB   get)r,   rJ   rK   payloadrM   responseparsedrN   s           r.   _send_requestzLocalMCPClient._send_requesta   s     <== --/l	
 <.

 ))..txxgw.WW!!#))(--8>!8HIIf7OEEIIf,=+>b9AU@VWXXzz(B'' Xs   A!C9#C7$BC9c                h  K   t        j                  d      | _        | j                  dk(  rS| j	                  ddi i dddd	d
       d{   }|j                  di       | _        |j                  di       | _        ni	 | j                  j                  | j                   d       d{   }|j                  dk(  r|j                         | _        nd|j                  i| _        d| _        t        d| j                   d| j                          y7 7 k# t        $ r ddi| _        Y Ew xY ww)zInitialize the MCP session.g      >@)timeoutr   
initializez
2024-11-05)rootssamplingzlocal-ollama-chatbotz0.1.0)nameversion)protocolVersionr%   
clientInfoN
serverInfor%   z/health   statusunknownTu'   ✅ Connected to MCP server (transport=z) at )httpxAsyncClientr#   r   rY   rU   r$   r%   r   status_coder@   	Exceptionr"   print)r,   init_resultresps      r.   connectzLocalMCPClient.connect|   s0     ,,T:>>U" $ 2 2'3.0b$A+Ag"V! K  +|R@D + CD9!--11T]]O72KLL##s*'+yy{D$(0$2B2B'CD$  5dnn5E F--"	
) M
  9$,i#8 9sH   A
D2D3D2+D ,D-<D ).D2D D/,D2.D//D2c                  K   | j                   st        d      | j                  dk(  r=| j                  di        d {   }|j	                  dg       | _        | j
                  S | j                  j	                  | j                   d       d {   }|j                          |j                         | _        | j
                  S 7 7 7w)Nz-connect() must be called before listing toolsr   z
tools/listr+   z/tools)
r"   rR   r   rY   rU   r&   r#   r   rT   r@   )r,   rQ   rm   s      r.   
list_toolszLocalMCPClient.list_tools   s     NOO>>U"--lB??F#ZZ4DN
 ~~ ))--v.FGGD!!#!YY[DN~~ @ Hs"   <CCACC	6C	Cc                  K   | j                   st        d      | j                  dk(  rv| j                  d||d       d {   }|j	                  dg       }|r.t        |t              r|d   }t        |t              r	d|v r|d   S t        j                  |d	      S d
|i}|r|j                  |       | j                  j                  | j                   d|       d {   }|j                          |j                         }t        |t              r |r|d   }t        |t              r	d|v r|d   S t        j                  |d	      S 7 7 nw)Nz+connect() must be called before using toolsr   z
tools/call)r_   	argumentscontentr   rB      indentr_   z/tools/call_tool)r@   )r"   rR   r   rY   rU   
isinstancelistdictr@   dumpsupdater#   rS   r   rT   )	r,   r_   rr   rQ   rs   firstrV   rm   datas	            r.   	call_toolzLocalMCPClient.call_tool   s9    LMM>>U"--lTXa<bccFjjB/G:gt4
eT*v =(::fQ//4.NN9%%%**dmm_<L+MT[*\\yy{dD!dGE%&6U?V}$zz$q))% d ]s%   ?EEB!E#E$A,EEc                   K    j                          d{    t        j                  dt        d      t        d      g      }g } j                  D ]J  }|d   }|j                  dd      }|j                  d	i       }d fd
}|j                   ||||             L | _        t         j                   j                  |      }t        | j                  d       _        t        dt         j                         d       y7 w)z7Expose MCP tools as LangChain tools and build an agent.N)systemzYou are a LangChain agent that can call MCP tools hosted by the recommender server. Use the tools when they are relevant and explain your reasoning briefly.messages)variable_nameagent_scratchpadr_   descriptionzNo description provided.inputSchemac                z     t         |      d fd       }| dt        j                  |d       |_        |S )N)r   c                 D   K   j                  |        d {   S 7 wN)r~   )kwargsr_   r,   s    r.   dynamic_toolzMLocalMCPClient.setup_langchain_agent.<locals>.make_tool.<locals>.dynamic_tool   s     !%f!====s     z


Schema: rt   ru   )r   r   returnstr)r   r@   rz   __doc__)r_   descschemar   r,   s   `   r.   	make_toolz7LocalMCPClient.setup_langchain_agent.<locals>.make_tool   sD    d-> .> +/|DJJvVW<X;Y'Z$##r0   T)agentr+   verboseu    🤖 LangChain agent ready with z MCP-powered tools.)r_   r   r   r   r   Dict[str, Any])rp   r
   from_messagesr   r&   rU   appendr+   r	   r)   r   r*   rk   len)	r,   promptlangchain_toolsspec	tool_namer   r   r   r   s	   `        r.   setup_langchain_agentz$LocalMCPClient.setup_langchain_agent   s     oo#11
 $*=#2DE

 NNDVI((=2LMKXXmR0F$ ""9YV#LM # %
)$((DJJG+%tzzSWX0TZZ0AATUVC 	 s   DDC4Dc                  K   | j                   st        d      t        d       	 	 t        d      j	                         }|s|j                         dv ryt        j                  | j                   j                  |g d       d{   }t        d|j                  d              7 "# t        $ r t        d	       Y yw xY ww)
z)Simple REPL to drive the LangChain agent.z-setup_langchain_agent() must be called first.z 
Type messages (Ctrl+C to exit).u   
💬 You: >   qexitquit)inputr   Nu
   🤖 Bot: outputz
Exiting chat...)r*   rR   rk   r   r<   r   asyncio	to_threadinvokerU   KeyboardInterrupt)r,   queryrW   s      r.   	chat_loopzLocalMCPClient.chat_loop   s     ""NOO12
n-335;;=$99!(!2!243F3F3M3MY^lnOo!pp
8<<#9":;<  q$ )*sR   $CB+ CB+ C0B+ B)!B+ (C)B+ +C?CCCc                   K   | j                   r)| j                   j                          d {    d | _         t        d       y 7 w)Nu"   🔌 Disconnected from MCP server.)r#   acloserk   r7   s    r.   
disconnectzLocalMCPClient.disconnect   s=     ""))+++#D23 ,s   *AAA)Nr   )r   r   r   zOptional[str]r   r   )r   r   )r   int)rB   r   r   Optional[Dict[str, Any]]r   )rJ   r   rK   r   r   r   r   None)r   zList[Dict[str, Any]])r_   r   rr   r   r   r   )__name__
__module____qualname__r   r/   r   r8   staticmethodrF   rY   rn   rp   r~   r   r   r    r0   r.   r   r   )   sr    Q
 #'	   	@  (6
:
*2#WJ&4r0   r   c                   K   t        j                  dd      } t        j                  d      }t        j                  dd      }t        | ||      }	 |j                          d {    |j	                          d {    |j                          d {    |j                          d {    y 7 M7 77 !7 # |j                          d {  7   w xY ww)NMCP_SERVER_URLz0https://agentcom-recommender.hf.space/proxy/7860MCP_AUTH_KEYMCP_TRANSPORTr   )r'   r(   r   rn   r   r   r   )r   r   r   clients       r.   mainr      s     yy:H yy(H		/62IHh	:F"nn**,,,   !!!	 	, !f!!!sr   ACB; %B3&B; =B5>B; B7B; C-B9.C3B; 5B; 7B; 9C;CCCC__main__r   )r   
__future__r   r   r@   r'   typingr   r   r   r   rg   langchain.agentsr   r	   langchain_core.promptsr
   r   langchain_core.toolsr   langchain_ollamar   dotenvr   rj   r   r   r   runr   r0   r.   <module>r      s   $ #   	 , ,  E J % '	"M
Q4 Q4h"" zGKK S  		s   A; ;BB