Skip to content

Commit 7ad44bd

Browse files
committed
Fix MCP update_memory metadata handling and database sync
Fixed three critical bugs in MCP update_memory tool: 1. SQLAlchemy mutation tracking: Create new dict object instead of modifying and reassigning same reference (change detection failed) 2. Empty metadata clearing: Filter system fields first, then update with request metadata. Empty {} now correctly clears custom fields while preserving system fields (source_app, mcp_client, etc.) 3. Database synchronization: Prepare merged metadata once and send same data to both mem0 (Qdrant) and PostgreSQL. Previously they received different metadata causing divergence. Behavior: - metadata=None: PostgreSQL unchanged, Qdrant preserved by mem0 core - metadata={}: Clear custom fields, keep system fields in both DBs - metadata={"key": "val"}: System + custom fields in both DBs Pattern follows add_memories: prepare combined metadata once, send to both databases for perfect synchronization.
1 parent 84a2655 commit 7ad44bd

File tree

1 file changed

+27
-10
lines changed

1 file changed

+27
-10
lines changed

openmemory/api/app/mcp_server.py

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import json
2121
import logging
2222
import uuid
23+
from typing import Optional
2324

2425
from app.database import SessionLocal
2526
from app.models import Memory, MemoryAccessLog, MemoryState, MemoryStatusHistory
@@ -289,7 +290,7 @@ async def list_memories() -> str:
289290

290291

291292
@mcp.tool(description="Update a memory's content and custom metadata")
292-
async def update_memory(memory_id: str, text: str, metadata: dict = {}) -> str:
293+
async def update_memory(memory_id: str, text: str, metadata: Optional[dict] = None) -> str:
293294
uid = user_id_var.get(None)
294295
client_name = client_name_var.get(None)
295296
if not uid:
@@ -318,17 +319,33 @@ async def update_memory(memory_id: str, text: str, metadata: dict = {}) -> str:
318319
if not check_memory_access_permissions(db, memory, app.id):
319320
return "Error: No permission to update this memory"
320321

321-
# Update in mem0 using public API (handles metadata protection automatically)
322-
response = memory_client.update(memory_id=memory_id, data=text, metadata=metadata)
322+
# Prepare metadata update (merge with system fields)
323+
metadata_for_vector_store = None
324+
if metadata is not None:
325+
# System fields that should never be overwritten
326+
SYSTEM_FIELDS = ['user_id', 'agent_id', 'run_id', 'actor_id', 'role', 'source_app', 'mcp_client']
323327

324-
# Update in database
325-
memory.content = text
326-
327-
# Merge custom metadata (preserve existing, update/add new)
328-
if metadata:
328+
# Start with only system fields from existing metadata
329329
existing_metadata = memory.metadata_ or {}
330-
existing_metadata.update(metadata)
331-
memory.metadata_ = existing_metadata
330+
new_metadata = {k: v for k, v in existing_metadata.items() if k in SYSTEM_FIELDS}
331+
332+
# Add all custom fields from request (empty request = clear custom metadata)
333+
new_metadata.update(metadata)
334+
335+
metadata_for_vector_store = new_metadata
336+
337+
# Update in mem0 using public API (with merged metadata)
338+
# If metadata_for_vector_store is None, mem0 will preserve existing metadata in Qdrant
339+
response = await memory_client.update(
340+
memory_id=memory_id,
341+
data=text,
342+
metadata=metadata_for_vector_store
343+
)
344+
345+
# Update in database (same merged metadata)
346+
memory.content = text
347+
if metadata is not None:
348+
memory.metadata_ = metadata_for_vector_store
332349

333350
# Create history entry
334351
history = MemoryStatusHistory(

0 commit comments

Comments
 (0)