
    jKj8                       U 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 ddl	m
Z
mZmZmZmZ ddlmZ ddlmZ dd	lmZ dd
lmZmZ ddlmZmZmZ ddlmZ ddlmZ ddl m!Z!  e"       Z#de$d<   da%de$d<    ejL                         Z'd*dZ(d+dZ)d,dZ*d-dZ+d.d/dZ,d0dZ-	 	 	 	 	 	 d1dZ.d2dZ/d3dZ0	 	 	 	 	 	 	 	 	 	 	 	 d4dZ1	 	 	 	 	 	 	 	 	 	 d5dZ2	 	 	 	 	 	 	 	 	 	 	 	 	 	 d6dZ3dddddddd	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d7d Z4d8d!Z5d9d"Z6d:d#Z7d;d$Z8dddd%	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d<d&Z9ddd'ddddd(	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d=d)Z:y)>a  Shared tool-call / tools-list / initialize lifecycle used by both the FastMCP
and low-level server adapters. The adapters resolve transport-specific details
(client info, session id, raw result shape) and delegate the analytics flow here
so both stay in sync.    )annotationsN)datetimetimezone)AnyDictListOptionalSet   )capture_event)MCPAnalyticsEventType)capture_exception)resolve_tool_call_intentset_event_intent)MCPAnalyticsDatahandle_identifyresolve_event_properties)log)build_captured_mcp_parameters)resolve_session_idzSet[Any]_BACKGROUND_TASKSz#Optional[asyncio.AbstractEventLoop]_bg_loopc                     t         at        5  t         Ft        j                         } t	        j
                  | j                  dd      j                          | a d d d        t         S t         S # 1 sw Y   t         S xY w)Nzposthog-mcp-captureT)targetnamedaemon)r   _bg_loop_lockasyncionew_event_loop	threadingThreadrun_foreverstart)loops    m/Users/ahmed/devFolder/Ultron/claude-voice/.venv/lib/python3.12/site-packages/posthog/mcp/_instrumentation.py_get_background_loopr&   &   se    --/  ++2GPT%'  O8O  Os   AA..A<c                    t         j                  |        	 | j                         s.| j                         t	        d| j                                 y y y # t
        $ r Y y w xY w)Nz background capture task failed: )r   discard	cancelled	exceptionr   	Exception)tasks    r%   _on_task_doner-   3   s^    d#~~DNN$4$@24>>3C2DEF %A s   <A 	A"!A"c                `   | y	 t        j                          t        j                  |       }t
        j                  |       |j                  t               y# t        $ rK t        j                  | t	                     }t
        j                  |       |j                  t               Y yw xY w)u   Schedule a capture coroutine without blocking the tool path. No-ops if the
    coroutine is ``None`` (no sink). Runs on the current loop when there is one,
    otherwise on a shared daemon loop (sync hosts) — never creates a throwaway loop.N)
r   get_running_loopRuntimeErrorrun_coroutine_threadsafer&   r   addadd_done_callbackr-   ensure_future)corofuturer,   s      r%   fire_and_forgetr7   <   s     |  "   &D$=)  11$8L8NOf%  /s   A AB-,B-c                   K   g } t        t              D ]  }t        |t        j                        r#|j                         r.| j                  |       @t        |t        j                  j                        se|j                         rv| j                  t        j                  |              | rt        j                  | ddi d{    yy7 w)u?  Await in-flight capture work before ``posthog.shutdown()`` instead of racing a
    sleep. Covers both paths: ``asyncio.Task`` (running-loop hosts) and the
    ``concurrent.futures.Future`` scheduled on the background loop (sync hosts like
    PostHogMCP) — the latter wrapped so it can be awaited on the current loop.return_exceptionsTN)listr   
isinstancer   Taskdoneappend
concurrentfuturesFuturewrap_futuregather)
awaitablests     r%   drain_pendingrF   O   s     
 J#$a&668!!!$:--445668!!'"5"5a"89 % nnjADAAA As$   ?C6C9C
A C
CCc                    t        t              D cg c]8  }t        |t        j                  j
                        r|j                         s|: }}|r"t        j                  j                  ||        yyc c}w )u   Block until background-loop captures finish. For sync hosts (PostHogMCP) that
    can't await :func:`drain_pending` — call it before ``flush()``/``shutdown()`` so
    trailing events aren't still in flight when the client tears down.)timeoutN)r:   r   r;   r?   r@   rA   r=   wait)rH   rE   r@   s      r%   drain_pending_syncrJ   `   sp     '((Aa++223AFFH 	
(  
 9 s   =A6c                f    t        | t              r| j                  d      du S t        | dd      du S )zIMCP tool results signal errors via ``isError: true`` rather than raising.isErrorTN)r;   dictgetgetattr)results    r%   is_tool_result_errorrQ   m   s4    &$zz)$,,69d+t33    c                    d| |xs i ddS )Nz
tools/callr   	argumentsmethodparams rT   s     r%   build_tool_call_requestrZ   t   s     io2> rR   c                f   t        | d      r	 | j                  d      S t	        | t
        t        f      r| D cg c]  }t        |       c}S t	        | t              r-| j                         D ci c]  \  }}|t        |       c}}S | S # t        $ r t        |       cY S w xY wc c}w c c}}w )N
model_dumpjsonmode)
hasattrr\   r+   strr;   r:   tuple_to_jsonablerM   items)objitemkeyvalues       r%   rc   rc   }   s    sL!	>>v>.. #e}%/23stT"s33#t;>99;G;ZS%\%((;GGJ  	s8O	 4Gs   B B(2B-B%$B%c                D    t        |       }t        |t              rd|iS |S )zShape a tool result into the ``{content: [...]}`` form the sanitizer
    understands (so image/audio/blob blocks get redacted).content)rc   r;   r:   )rP   
serializeds     r%   _wrap_responserl      s)     f%J*d#:&&rR   c                  K   || j                   v ry| j                  |       t        j                  |||t	        j
                  t        j                        d}t        | |di d|       d{    t        t        | |             y7 w)zLazily emit ``$mcp_initialize`` once per session. The Python MCP SDK handles
    ``InitializeRequest`` inside the session layer (not ``request_handlers``), so we
    synthesize the event from the first instrumented request that carries client info.N)
event_type
session_idclient_nameclient_version	timestamp
initializerV   )initialized_sessionsmark_session_initializedr   MCP_INITIALIZEr   nowr   utc_apply_event_propertiesr7   r   )dataro   rp   rq   extraevents         r%   _maybe_emit_initializer}      s      T...!!*-+:: "(\\(,,/E "e;U   M$./s   A,B.B	/Bc                J   K   t        | ||       d{   }|||d<   yy7 w)u   Resolve the customer's ``event_properties`` callback and stamp it onto the
    event — applied to every auto-captured event type, matching the TS SDK.N
properties)r   )rz   r|   requestr{   propss        r%   ry   ry      s4      +4%@@E#l  As   #!#c                  K   t        | |       d{   }t        | |||       d{   }|rt        t        | |             t	        | ||||       d{    |S 7 J7 67 
w)u.  Resolve the session id, run identify, then lazily emit initialize. Returns
    the session id to stamp on the event for this request.

    Identify runs *before* initialize so the resolved identity is already in the cache
    when ``capture_event`` builds the initialize event — otherwise the first
    ``$mcp_initialize`` is anonymous even when identify resolves on the same request.
    (Still not byte-parity with the TS SDK, which wraps the real initialize handler;
    the Python SDK handles initialize in the session layer, not ``request_handlers``.)N)r   r   r7   r   r}   )rz   mcp_session_idrp   rq   r   r{   ro   identify_events           r%   prepare_requestr      sj     " *$??J*4WeLLNdN;<
 z;PU
VVV @L Ws1   A#AA#A-A#A!A#A#!A#)rP   errorduration_msrp   rq   conversation_idr{   c       	        L  K   	 t        ||      }t        j                  ||| j                  j	                  |      | j
                  j	                  |      t        |      ||||	dd}t        |t        | ||
       d {          |d|d<   t        |      |d<   n.|,t        |      |d<   t        |      rd|d<   t        |      |d<   t        | ||
       d {   }|||d<   t        t        | |             y 7 ~7 ## t        $ r}t!        d|        Y d }~y d }~ww xY ww)	NF)rn   ro   resource_nametool_descriptiontool_category
parametersdurationrp   rq   r   is_errorTr   r   responser   z:record_tool_call failed (event dropped, tool unaffected): )rZ   r   MCP_TOOLS_CALLtool_descriptionsrN   tool_categoriesr   r   r   r   rl   rQ   r   r7   r   r+   r   )rz   ro   r   rU   rP   r   r   rp   rq   r   r{   r   r|   r   errs                  r%   record_tool_callr      s<      P)$	:/>>$! $ 6 6 : :4 @!1155d;7@#&,.!
 	&>tWe&T TU $E*.u5E'N .v 6E*#F+$(j!!26!:g.tWeDD"'E,dE23 !U E
  PHNOOPsT   D$A9D  =C<
>AD  C> D  ;D$<D  >D   	D!	DD$D!!D$c                P    t        | d|       }t        t        |dg       xs g       S )uz   Pull the tool list out of a ListTools ServerResult (a copy — to MUTATE the
    real list use ``append_get_more_tools``).roottools)rO   r:   )rP   r   s     r%   extract_toolsr     s+     666*Dgr*0b11rR   c                    ddl m} ddlm}  ||      }|j	                  |d   |d   |d   |d   	      }t        | d
|       }t        |dd      }t        |t              r|j                  |       yy)zNAppend the get_more_tools virtual tool to the real ListToolsResult.tools list.r   Nr   )build_report_missing_descriptorr   descriptioninputSchemar   )r   r   r   r   r   r   )		mcp.typestypesr   r   ToolrO   r;   r:   r>   )rP   r   	mcp_typesr   
descriptortoolr   
tools_lists           r%   append_get_more_toolsr     s    !606J>>}-}-}-	  D 666*Dw-J*d#$ $rR   c                    t        | dd      }t        |t              r#|j                  d      }t        |t              r|S y)z;Read a tool's product category from its ``_meta.category``.metaNcategory)rO   r;   rM   rN   ra   )r   r   r   s      r%   read_tool_categoryr      s:    4&D$88J'h$OrR   c                    t        | dd      xs d}t        | dd      }i }|t        |d      r	 |j                  d      }||dS # t        $ r i }Y w xY w)	zHShape a request object into the JSON-RPC-ish dict the sanitizer expects.rW   Nz
tools/listrX   r\   r]   r^   rV   )rO   r`   r\   r+   )reqrW   rX   params_dicts       r%   request_to_dictr   *  sr    S(D)9\FS(D)FKgfl;	 +++8K 44  	K	s   A AA)rp   rq   r{   c               x  K   	 t        ||      }t        j                  ||t        |      ||d}	t	        |t
              r(|j                         r|j                         |	d<   d|	d<   t        | |	||       d{    t        t        | |	             y7 # t        $ r}
t        d|
        Y d}
~
yd}
~
ww xY ww)zuRecord a ``get_more_tools`` call as ``$mcp_missing_capability``, with the
    agent's stated need as ``$mcp_intent``.)rn   ro   r   r   rp   rq   user_intentcontext_parameteruser_intent_sourceNz2record_missing_capability failed (event dropped): )rZ   r   MCP_MISSING_CAPABILITYr   r;   ra   stripry   r7   r   r+   r   )rz   ro   	tool_namecontextrU   rp   rq   r{   r   r|   r   s              r%   record_missing_capabilityr   7  s     H))Y?/FF$&7@&,!
 gs##*==?E- *=E&'%dE7EBBBdE23 	C H@FGGHsA   B:A5B 9B:B B:B 	B7B2-B:2B77B:F)r   r   r   r   rp   rq   r{   c       	        t  K   	 t         j                  ||t        |      |t        |      nd |||	|t	        j
                  t        j                        d
}|t        |      |d<   t        | |||
       d {    t        t        | |             y 7 # t        $ r}t        d|        Y d }~y d }~ww xY ww)N)
rn   ro   listed_tool_namesr   r   r   rp   rq   r   rr   r   z*record_tools_list failed (event dropped): )r   MCP_TOOLS_LISTr   rl   r   rw   r   rx   r   ry   r7   r   r+   r   )rz   ro   namesr   r   r   r   r   rp   rq   r{   r|   r   s                r%   record_tools_listr   W  s     @/>>$!&7@4<4Hx0d#&, !hll3!
 .u5E'N%dE7EBBBdE23 	C @8>??@sA   B8A3B 7B8B B8B 	B5B0+B80B55B8)returnzasyncio.AbstractEventLoop)r,   r   r   None)r5   zOptional[Any]r   r   )r   r   )N)rH   Optional[float]r   r   )rP   r   r   bool)r   ra   rU   Optional[Dict[str, Any]]r   Dict[str, Any])re   r   r   r   )rP   r   r   r   )rz   r   ro   ra   rp   Optional[str]rq   r   r{   r   r   r   )
rz   r   r|   r   r   r   r{   r   r   r   )rz   r   r   r   rp   r   rq   r   r   r   r{   r   r   ra   )rz   r   ro   ra   r   ra   rU   r   rP   r   r   r   r   r   rp   r   rq   r   r   r   r{   r   r   r   )rP   r   r   r:   )rP   r   r   ra   r   r   )r   r   r   r   )r   r   r   r   )rz   r   ro   ra   r   ra   r   r   rU   r   rp   r   rq   r   r{   r   r   r   )rz   r   ro   ra   r   z	List[str]r   r   r   r   r   r   r   r   r   r   rp   r   rq   r   r{   r   r   r   );__doc__
__future__r   r   concurrent.futuresr?   r    r   r   typingr   r   r   r	   r
   _capturer   _event_typesr   _exceptionsr   _intentr   r   	_internalr   r   r   loggerr   _sanitizationr   sessionr   setr   __annotations__r   Lockr   r&   r-   r7   rF   rJ   rQ   rZ   rc   rl   r}   ry   r   r   r   r   r   r   r   r   rY   rR   r%   <module>r      s%  

 #    ' 1 1 # / * ? R R  8 '
 "e 8 # 15
- 4	 
*&B"
:4
2
0
00 0 "	0
 $0 
04
$

$
$ 
$ $	
$
 

$
 " 	
 "  $ 	> #'!%$(%)&*0P
0P0P 	0P
 (0P 0P 0P !0P 0P "0P #0P $0P 
0Pf2 &
5( "&$(&*H
HH 	H
 H (H H "H $H 
HL #'!%$(&* @
 @ @ 	 @
  @  @ ! @  @  @  @ " @ $ @ 
 @rR   