+
    Wj              	         R t ^ RIHt ^ RIt^ RIt^ RIt^ RIHt ^ RIt^ RI	H
t
 ^ RIHt ^ RIHt ]P                  P!                  RR4      tR	] 2t]'       d6   ]P&                  ! ]P)                  4       4      P+                  4       R
,          MRt]
! RRR]! RR7      RR7      t]P1                  4       R$R R ll4       t]P1                  4       R%R R ll4       t]P1                  4       R&R R ll4       t]P1                  4       R R l4       t]P1                  4       R R l4       t]P=                  4       t]P@                  t ]R 4       t!R R lt"R R lt# ! R  R!4      t$R" R# lt%R# )'ud  MCP (Model Context Protocol) endpoint for the Jarvis memory service.

Mounts a SINGLE streamable-HTTP MCP endpoint onto the same FastAPI app that
serves the REST API, so Claude Code and the claude.ai app can share the one
memory brain. The tools are THIN wrappers that call app.py's OWN functions in
process (never over HTTP to ourselves, no logic duplicated).

Two doors, same endpoint (auth enforced HERE, before the MCP handler runs):
  • POST /mcp            — Authorization: Bearer $MEMORY_API_KEY   (Claude Code --header)
  • POST /mcp/<token>    — token = first 32 hex of sha256(MEMORY_API_KEY)
                           (path-secret door for claude.ai custom connectors,
                           whose UI can't send custom headers; OAuth is overkill
                           for a personal service).
If MEMORY_API_KEY is empty, BOTH doors are open (matches app.py `_auth`).

Wiring (in app.py): `import mcp_server`, run `mcp_server.lifespan()` from the
FastAPI lifespan (the session manager needs its task group running), and call
`mcp_server.mount(app)` to attach the two routes. Nothing else changes.
)annotationsN)asynccontextmanager)FastMCP)TransportSecuritySettings)RouteMEMORY_API_KEY zBearer :N    Nzjarvis-memoryTF)enable_dns_rebinding_protectionu   Ahmed's shared long-term memory (a fact graph). Search it before answering from scratch, save durable facts, and checkpoint projects so any Claude — Code or app — can pick up where the last one stopped.)stateless_httpjson_responsetransport_securityinstructionsc               0    V ^8  d   QhRRRRRRRRRRR	R
/# )   querystrkint	when_from
str | Nonewhen_toas_ofreturndict )formats   "mcp_server.py__annotate__r   ?   s9     3 3s 3s 3: 3!+3)3593    c                   a aaaaa"   ^ RI o\        P                  P                  VVVV VV3R l4      G Rj  xL
 #  L5i)zSearch shared memory: salience-ranked facts plus their connected context.

Optional temporal filters (all ISO dates): `when_from`/`when_to` window the
EVENT date a fact refers to; `as_of` answers "what was true THEN" bi-temporally.
Nc                 @   < S P                  SSR ^RSSS\        R7	      # )NF)	qr   grouphopsinclude_staler   r   r   authorization)search_BEARER)appr   r   r   r   r   s   r   <lambda>memory_search.<locals>.<lambda>I   s(    

Uat!).e%.)0  2r   r)   anyio	to_threadrun_sync)r   r   r   r   r   r)   s   fffff@r   memory_searchr0   >   s6      ))	2 	23 3 3 3s   0?=?c               0    V ^8  d   QhRRRRRRRRRR	R
R/# )r   textr   kindentitieszlist[str] | Nonewhenr   
importancer   r   r   r   )r   s   "r   r   r   P   s>     ; ; ;3 ;$4; *; '*; 37;r   c           	     J  aa"   ^ RI oT;'       g    .  Uu. uF(  p\        V4      P                  4       '       g   K$  RV/NK*  	  ppSP                  Y;'       g    RRT;'       g    RW4R7      o\        P
                  P                  VV3R l4      G Rj  xL
 # u upi  L	5i)u   Save one durable fact; `entities` are names (people/companies/projects) to attach.

`when` is the ISO date the fact REFERS to — event time, not now (e.g. a meeting
date), used for temporal recall. `importance` (1-10, default 5) weights recall.
Nnamefactmcp)r2   r3   sourcer4   r5   r6   c                 2   < S P                  S\        R 7      # )r&   rememberr(   r)   bodys   r   r*   !memory_remember.<locals>.<lambda>^       T9r   )r)   r   stripRememberr-   r.   r/   )	r2   r3   r4   r5   r6   eentsr)   rA   s	   &&&&&  @@r   memory_rememberrH   O   s      "*..b.D.ASV\\^KVQK.DD<<T!%D  QD))9; ; ; E;s-   B#!BBB#B#'0B#B!
B#c          
     ,    V ^8  d   QhRRRRRRRRRR/# )	r   projectr   done
next_stepsdetailsr   r   r   r   )r   s   "r   r   r   b   s1     ; ;c ; ;# ;&0;<@;r   c           	     L  aa"   ^ RI oRV  RV RV R2pV'       d1   VP                  4       '       d   VRVP                  4        2,          pSP                  VRRR	V R
R/.SP                  4       R7      o\        P
                  P                  VV3R l4      G Rj  xL
 #  L5i)zPSave a 'where we stopped' checkpoint for a project: what's DONE and what's NEXT.NzPROJECT z checkpoint: DONE z. NEXT . 
checkpointr:   r8   typerJ   )r2   r3   r;   r4   r5   c                 2   < S P                  S\        R 7      # r=   r>   r@   s   r   r*   $project_checkpoint.<locals>.<lambda>o   rC   r   )r)   rD   rE   _now_isor-   r.   r/   )rJ   rK   rL   rM   r2   r)   rA   s   &&&& @@r   project_checkpointrV   a   s      gY0gj\KD7==??!GMMO$%% <<TU#)7FI"F!G \\^  -D ))9; ; ; ;s   .B$A*B$B"B$c                    V ^8  d   QhRRRR/# r   r8   r   r   r   r   )r   s   "r   r   r   s   s     J Jc Jd Jr   c                r   a a"   ^ RI o\        P                  P                  VV 3R l4      G Rj  xL
 #  L5i)zNPull up everything memory knows about one entity (person / company / project).Nc                 4   < S P                  SR \        R7      # N)r8   r#   r&   )entityr(   r)   r8   s   r   r*   entity_lookup.<locals>.<lambda>w   s    

D
Hr   r,   r8   r)   s   f@r   entity_lookupr`   r   s3      ))HJ J J J   ,757c                    V ^8  d   QhRRRR/# rX   r   )r   s   "r   r   r   {   s     K Ks Kt Kr   c                r   a a"   ^ RI o\        P                  P                  VV 3R l4      G Rj  xL
 #  L5i)zFull dossier of one entity (person/company/concept): summary, contacts, relations, timeline of every interaction, open tasks. Use when Ahmed asks 'tell me about / show me X' or before a meeting with X.Nc                 4   < S P                  SR \        R7      # r[   )profiler(   r]   s   r   r*    entity_profile.<locals>.<lambda>   s    TIr   r,   r_   s   f@r   entity_profilerg   z   s3      ))IK K K Kra   c                   "   \         P                  4       ;_uu_4       GRj  xL
  R5x  RRR4      GRj  xL
  R#  L L  + GRj  xL 
 '       g   i     R# ; i5i)zIEnter from the FastAPI lifespan so the session manager's task group runs.N)session_managerrunr   r   r   lifespanrk      s0      ""$$$ %$$$$$sF   !A ?A AA AA A A		A

A	A		A c                    V ^8  d   QhRRRR/# )r   tokenr   r   boolr   )r   s   "r   r   r      s     J Jj JT Jr   c                ^   \         '       g   R # Ve   \        P                  ! V\        4      # V P	                  R. 4       UUu/ uF2  w  r#VP                  4       P                  4       VP                  4       bK4  	  ppp\        P                  ! VP	                  RR4      \        4      # u uppi )Theadersr&   r   )API_KEYhmaccompare_digest
PATH_TOKENgetdecodelowerr(   )scoperm   r   vrp   s   &&   r   _authorizedrz      s    7""5*55 99Y35341 xxz!188:-3  5w{{?B?II5s   8B)c                   V ^8  d   QhRR/# r   r   Noner   )r   s   "r   r   r      s     T T4 Tr   c                l   "   V ! R RRRRR	./4      G Rj  xL
  V ! R RRR/4      G Rj  xL
  R#  L L5i)
rR   zhttp.response.startstatusi  rp   Nzhttp.response.bodyrA   s   {"error":"unauthorized"})s   content-types   application/jsonr   )sends   &r   _rejectr      sT     
-xBCE F F F
,f6QR
SSSFSs   404244c                  ,    ] tR t^tRtR R ltR tRtR# )_DoorzHASGI gate in front of the shared MCP handler. mode: 'header' or 'token'.c                   V ^8  d   QhRR/# )r   moder   r   )r   s   "r   r   _Door.__annotate__   s      S r   c                	    Wn         R # Nr   )selfr   s   &&r   __init___Door.__init__   s    	r   c                	P  "   VR ,          R8w  d   \        V4      G Rj  xL
  R# V P                  R8X  d"   VP                  R/ 4      P                  R4      MRp\        W4      '       g   \        V4      G Rj  xL
  R# \        P                  WV4      G Rj  xL
  R#  L L( L
5i)rR   httpNrm   path_params)r   r   ru   rz   ri   handle_request)r   rx   receiver   rm   s   &&&& r   __call___Door.__call__   s     =F"$-II( ="-11':.2 	5(($-,,UTBBB  
  Bs4   B&B AB&9B":B&B$B&"B&$B&r   N)__name__
__module____qualname____firstlineno____doc__r   r   __static_attributes__r   r   r   r   r      s    R	Cr   r   c                   V ^8  d   QhRR/# r|   r   )r   s   "r   r   r      s     D D$ Dr   c                    V P                   P                  P                  \        R\	        R4      4      4       V P                   P                  P                  \        R\	        R4      4      4       R# )z=Attach both MCP doors to an existing FastAPI / Starlette app.z/mcpheaderz/mcp/{token}rm   N)routerroutesappendr   r   )r)   s   &r   mountr      sH    JJU65?;<JJU>5>BCr   )   NNN)r9   NN   r   )&r   
__future__r   hashlibrr   os
contextlibr   r-   mcp.server.fastmcpr   mcp.server.transport_securityr   starlette.routingr   environru   rq   r(   sha256encode	hexdigestrt   r:   toolr0   rH   rV   r`   rg   streamable_http_app_ri   rk   rz   r   r   r   r   r   r   <module>r      sl  & #   	 *  & C #
**..)2
.G9
 CJW^^GNN,-779#>r
 1(-/	Q. 3 3  ; ;" ; ;  J J K K %%  JTC C$Dr   