
    ^iUB                        U d Z ddlm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 ddlmZ ddlmZmZ ddlmZ dd	lmZ  ej*                  d
      ZdZddZ G d d      Zdaded<   dddZddZdddZy)u  RLM Neo-Cortex -- MCP Bridge.

Translates the MCP tool interface used by ``mcp-servers/sunaiva-memory/server.py``
into calls to the RLM MemoryGateway pipeline.

Why this exists
---------------
The existing Sunaiva Memory MCP server exposes six tools
(``memory_store``, ``memory_search``, ``memory_get_all``, ``memory_delete``,
``memory_summarize``, ``memory_ingest_takeout``) that write directly to Qdrant
and PostgreSQL, bypassing:

  - Surprise scoring (is this worth remembering?)
  - Tier routing (WORKING / EPISODIC / SEMANTIC)
  - Quota enforcement (per-tenant daily limits)
  - Tenant isolation via the EntitlementLedger
  - Ebbinghaus decay scheduling

The MCP bridge intercepts ``memory_store`` and ``memory_search`` calls and
routes them through the full RLM pipeline.  All other tools fall back to the
existing implementation so zero regressions occur.

Architecture::

    MCP Client (Claude / SubAIVA)
        │
        ▼
    MCPBridge  (this module)
        │  translate: memory_store → gateway.write_memory()
        │  translate: memory_search → gateway.search_memories()
        ▼
    MemoryGateway  (core.rlm.gateway)
        │  surprise scoring, tier routing, quota, dedup
        ▼
    Elestio (PostgreSQL + Qdrant + Redis)

Usage (standalone, without a running API server)::

    import asyncio
    from core.rlm.mcp_bridge import MCPBridge

    bridge = MCPBridge()
    asyncio.run(bridge.initialize())

    result = asyncio.run(bridge.memory_store(
        content="Customer prefers email contact over phone",
        user_id="550e8400-e29b-41d4-a716-446655440000",
    ))

Usage (as a drop-in replacement inside the MCP server)::

    from core.rlm.mcp_bridge import MCPBridge, get_bridge

    bridge = get_bridge()   # singleton, initialised lazily

    @mcp.tool()
    async def memory_store(content: str, user_id: str = "", metadata: str = "{}") -> str:
        return await bridge.memory_store(content, user_id, metadata)

Story: integration-layer-mcp-bridge
    )annotationsN)AnyDictListOptional)UUID   )EMBEDDING_DIM
MemoryTier)MemoryGateway)settingszcore.rlm.mcp_bridgec                    | r| j                         sy	 t        | j                               S # t        t        f$ r t        j                  d|        Y yw xY w)a  Convert a string user_id to UUID.

    Accepts:
    - Standard UUID strings: '550e8400-e29b-41d4-a716-446655440000'
    - Hex UUIDs without dashes:  '550e8400e29b41d4a716446655440000'

    Returns None for invalid / empty strings (bridge will fall back to legacy).
    Nu8   user_id '%s' is not a valid UUID — skipping RLM bridge)stripr   
ValueErrorAttributeErrorloggerdebug)user_ids    ,/mnt/e/genesis-system/core/rlm/mcp_bridge.py_resolve_tenantr   T   sO     '--/GMMO$$' OQXYs   . %AAc                      e Zd ZdZ	 d	 	 	 ddZddZddZedd       ZddZ		 	 	 	 d	 	 	 	 	 	 	 	 	 	 	 ddZ
	 	 	 d	 	 	 	 	 	 	 	 	 dd	Zddd
ZddZy)	MCPBridgeaa  Bridge between MCP tool calls and the RLM MemoryGateway pipeline.

    Lifecycle::

        bridge = MCPBridge()
        await bridge.initialize()
        # ... use bridge ...
        await bridge.close()

    Or use the module-level singleton (lazy init)::

        bridge = get_bridge()
        # initialize() is called automatically on first use
    Nc                    ||| _         d| _        d| _        yt        t        j                  t        j
                  t        j                  t        j                        | _         d| _        d| _        y)zCreate an MCPBridge.

        Args:
            gateway: An existing MemoryGateway instance.  If None, a new one
                     is created from environment variables via settings.
        NF)pg_dsn
qdrant_urlqdrant_api_key	redis_urlT)	_gateway_owns_gatewayr   r   database_urlr   r   r   _initialized)selfgateways     r   __init__zMCPBridge.__init__z   sg     #DM!&D " *,,#..'66",,	DM "&D!    c                   K   | j                   ry| j                  j                          d{    d| _         t        j	                  dt        | j                               y7 5w)ul   Open all backend connections.

        Safe to call multiple times — subsequent calls are no-ops.
        NTz"MCPBridge initialised (gateway=%s))r!   r   
initializer   infoidr"   s    r   r'   zMCPBridge.initialize   sO     
 mm&&((( 8"T]]:KL 	)s   +A%A#6A%c                   K   | j                   rL| j                  r?| j                  j                          d{    d| _        t        j                  d       yyy7 #w)z:Close backend connections if this bridge owns the gateway.NFzMCPBridge closed)r   r!   r   closer   r(   r*   s    r   r,   zMCPBridge.close   sM     $"3"3--%%''' %DKK*+ #4's   6AA$Ac                J    | j                   xr | j                  j                  S )z6True if the bridge (and its gateway) are ready to use.)r!   r   is_initializedr*   s    r   r.   zMCPBridge.is_initialized   s       AT]]%A%AAr%   c                Z   K   | j                   s| j                          d{    yy7 w)z&Initialise lazily if not already done.N)r!   r'   r*   s    r   _ensure_initzMCPBridge._ensure_init   s'       //### !#s    +)+c           	     B  K   | j                          d{    t        |      }	 |rt        j                  |      ni }|7t
        j                  d|       t        j                  ddddd| dd	d
      S 	 | j                  j                  |||||       d{   }|j                  t        j                  k7  }	t        j                  |j                  t        |j                        |j                  j                   t#        |j$                  d      |	|j&                  dd
      S 7 # t        j                  $ r d|i}Y 	w xY w7 # t(        $ rD}
t
        j+                  d|
       t        j                  ddt        |
      dd
      cY d}
~
S d}
~
ww xY ww)u  Translate an MCP memory_store call into gateway.write_memory().

        The full RLM pipeline is applied:
          surprise scoring → tier routing → quota check → dedup → storage.

        Args:
            content:  Memory text content (min 10 chars for RLM pipeline).
            user_id:  UUID string identifying the tenant.  If not a valid UUID,
                      the bridge falls back to the legacy direct-write path
                      (for backward compatibility with non-UUID user IDs).
            metadata: JSON string of additional metadata.
            source:   Origin label (default: 'mcp').
            domain:   Domain/topic category (default: 'general').

        Returns:
            JSON string with memory_id, tier, surprise_score, and storage flags.
        Nraw_metadatau>   memory_store: user_id '%s' is not a UUID — using legacy pathunknown        F	user_id 'z_' is not a valid UUID. RLM pipeline requires UUID tenant_id. Upgrade the caller to pass a UUID.)	memory_idtiersurprise_scorestoredwarning   indent)	tenant_idcontentsourcedomainmetadata   )r6   r>   r7   r8   r9   pg_idz!MCPBridge.memory_store failed: %s)r6   r9   error)r0   r   jsonloadsJSONDecodeErrorr   r:   dumpsr   write_memorymemory_tierr   DISCARD	vector_idstrr>   valueroundr8   rD   	ExceptionrE   )r"   r?   r   rB   r@   rA   r>   metarecordr9   excs              r   memory_storezMCPBridge.memory_store   s    2 !!!#G,		.;C4::h#7D NNPRY ::!!"%y )9 9
 
 
	==55# 6  F '':+=+==F::#-- !1!12**00"'(=(=q"A   K 	" ## 	."H-D	.,"  	LL<cB::!S 	 	su   FD,FD/  9F:#E EBE +F/E
F	E

FE 	F9FFFFFc                R  K   | j                          d{    t        |      }|5t        j                  d|       t	        j
                  g dd| ddd      S t        d	t        |d
            }	 | j                  j                  ||||       d{   }|D cg c]  }|j                  |j                  |j                  |j                  |j                  j                  t!        |j"                  d      |j$                  j'                         |j(                  d }}t	        j
                  |t+        |      ddt,              S 7 77 c c}w # t.        $ rD}	t        j1                  d|	       t	        j
                  g dt-        |	      dd      cY d}	~	S d}	~	ww xY ww)u  Translate an MCP memory_search call into gateway.search_memories().

        Performs 768-dim vector search via Qdrant with full tenant isolation.

        Args:
            query:     Natural-language query string.
            user_id:   UUID string identifying the tenant.
            limit:     Maximum results to return (default 10, max 100).
            min_score: Minimum similarity threshold (0.0–1.0).

        Returns:
            JSON string with list of memory results.
        NuE   memory_search: user_id '%s' is not a UUID — returning empty resultsr   r5   z<' is not a valid UUID. RLM pipeline requires UUID tenant_id.)resultscountr:   r;   r<   r	   d   )r>   querylimit	min_scorerC   )r6   r?   r@   rA   r7   r8   
created_atrB   )rW   rX   )r=   defaultz"MCPBridge.memory_search failed: %s)rW   rX   rE   )r0   r   r   r:   rF   rI   maxminr   search_memoriesrM   r?   r@   rA   rK   rO   rP   r8   r]   	isoformatrB   lenrN   rQ   rE   )
r"   rZ   r   r[   r\   r>   recordsrrW   rT   s
             r   memory_searchzMCPBridge.memory_search  s    ( !!!#G,	NNWY` ::y )< <	   As5#'	X MM99##	 :  G" !  "# yyhhhhMM//&+A,<,<a&@"#,,"8"8": !

	G  ::'CLIRS]`aaO 	"(  	XLL=sC::"q3s8LUVWW	Xse   F'EAF'4"E EE BE#)E F'E E 	F$ 9FF$F'F$$F'c                  K   | j                          d{    t        |      }|t        j                  dd| ddd      S 	 | j                  j                  ||       d{   }t        j                  ||d	d      S 7 o7  # t        $ rC}t        j                  d
|       t        j                  dt        |      dd      cY d}~S d}~ww xY ww)a!  Translate an MCP memory_delete call into gateway.delete_memory().

        Args:
            memory_id: The memory vector_id to delete.
            user_id:   UUID string identifying the tenant (for ownership check).

        Returns:
            JSON string with deletion status.
        NFr5   z' is not a valid UUID)deletedrE   r;   r<   )r>   r6   )rh   r6   z"MCPBridge.memory_delete failed: %s)
r0   r   rF   rI   r   delete_memoryrQ   r   rE   rN   )r"   r6   r   r>   rh   rT   s         r   memory_deletezMCPBridge.memory_deleteR  s      !!!#G,	:: $WI-BC  
	O MM77## 8  G ::'	JSTUU 	"
  	OLL=sC::%#c(CANN	OsQ   CB/C B
 'B(B
 CB
 
	C8CCCCCc                   K   | j                          d{    | j                  j                          d{   S 7 &7 w)ztReturn gateway health status dict.

        Returns:
            Dict with keys: status, pg, qdrant, redis.
        N)r0   r   health_checkr*   s    r   healthzMCPBridge.healths  s9      !!!]]//111 	"1s   A=!A?AAN)r#   Optional[MemoryGateway]returnNonerp   rq   )rp   bool z{}mcpgeneralr?   rN   r   rN   rB   rN   r@   rN   rA   rN   rp   rN   ru   
   r4   
rZ   rN   r   rN   r[   intr\   floatrp   rN   ru   r6   rN   r   rN   rp   rN   )rp   zDict[str, Any])__name__
__module____qualname____doc__r$   r'   r,   propertyr.   r0   rU   rf   rj   rm    r%   r   r   r   j   s    " ,0"(" 
"8	M, B B$ MM M 	M
 M M 
Ml ?X?X ?X 	?X
 ?X 
?XJOB2r%   r   Optional[MCPBridge]_bridge_singletonc                2    t         t        |       a t         S )aD  Return the module-level MCPBridge singleton.

    Creates it on first call.  Subsequent calls return the same instance
    regardless of the *gateway* argument (which is only used at creation).

    Args:
        gateway: Optional pre-built gateway (only used the first time).

    Returns:
        MCPBridge singleton.
    r#   )r   r   r   s    r   
get_bridger     s      %g6r%   c                     da y)z0Reset the singleton (for testing purposes only).N)r   r   r%   r   reset_bridger     s
     r%   c                *   |xs
 t               | j                         	 	 	 	 d	 	 	 	 	 	 	 	 	 	 	 dfd       }| j                         	 	 	 d	 	 	 	 	 	 	 	 	 d	fd       }| j                         	 d
	 	 	 	 	 dfd       }t        j                  d       y)a  Register RLM-backed MCP tools onto an existing FastMCP instance.

    Replaces the ``memory_store`` and ``memory_search`` tools on *mcp*
    with RLM-pipeline versions.  ``memory_get_all``, ``memory_summarize``,
    and ``memory_ingest_takeout`` are left as-is (they do not need RLM routing).

    Usage inside the MCP server::

        from mcp.server.fastmcp import FastMCP
        from core.rlm.mcp_bridge import register_rlm_tools

        mcp = FastMCP("Sunaiva Memory")
        register_rlm_tools(mcp)

        @mcp.tool()
        def memory_get_all(...): ...

    Args:
        mcp:    A FastMCP instance.
        bridge: Optional pre-built bridge (uses singleton if None).
    c                J   K   j                  | ||||       d{   S 7 w)a_  Store a memory through the RLM pipeline (surprise scoring + tier routing).

        Args:
            content:  Text content to remember.
            user_id:  Tenant UUID string.
            metadata: Optional JSON metadata string.
            source:   Origin label (default: 'mcp').
            domain:   Topic domain (default: 'general').
        N)rU   )r?   r   rB   r@   rA   bs        r   rU   z(register_rlm_tools.<locals>.memory_store  s&     " ^^GWhOOOOs   #!#c                H   K   j                  | |||       d{   S 7 w)u1  Semantic search via the RLM pipeline (768-dim Qdrant + tenant isolation).

        Args:
            query:     Natural language search query.
            user_id:   Tenant UUID string.
            limit:     Max results (1–100).
            min_score: Minimum similarity threshold (0.0–1.0).
        N)rf   )rZ   r   r[   r\   r   s       r   rf   z)register_rlm_tools.<locals>.memory_search  s$      __UGUIFFFFs   " "c                D   K   j                  | |       d{   S 7 w)zDelete a memory from all RLM backends.

        Args:
            memory_id: The memory vector_id to delete.
            user_id:   Tenant UUID string (ownership check).
        N)rj   )r6   r   r   s     r   rj   z)register_rlm_tools.<locals>.memory_delete  s       __Y8888s     zERLM MCP tools registered (memory_store, memory_search, memory_delete)Nrt   rx   ry   r{   r~   r   )r   toolr   r(   )rv   bridgerU   rf   rj   r   s        @r   register_rlm_toolsr     s   , 	*,AXXZ PPP P 	P
 P 
P P$ 	XXZ 	GGG G 	G
 
G G  	XXZ 
9
9
9 

9 
9 KKWXr%   )r   rN   rp   zOptional[UUID]rn   )r#   ro   rp   r   rr   )rv   r   r   r   rp   rq   )r   
__future__r   asynciorF   loggingostypingr   r   r   r   uuidr   	contractsr
   r   r#   r   configr   	getLoggerr   _INVALID_UUID_SENTINELr   r   r   __annotations__r   r   r   r   r%   r   <module>r      sz   <z #    	 , ,  0 " 			0	1  ,P2 P2n *. & -$IYr%   