Skip to content

add_memories function causes Pydantic validation errors when used with Claude Desktop via Supergateway #1

Description

@h6y3

OpenMemory MCP Server Validation Error Fix

Executive Summary

The OpenMemory MCP server contains a critical bug in the add_memories function that causes Pydantic validation errors when used with Claude Desktop via Supergateway. The function returns a Python dictionary object instead of a string, violating the MCP protocol specification and causing tool execution failures.

Problem Description

Error Symptoms

  • Users experience validation errors when using OpenMemory MCP tools in Claude Desktop
  • Error message: Error executing tool add_memories: 1 validation error for add_memoriesOutput result Input should be a valid string [type=string_type, input_value={'results': [{'id': '549e...', 'event': 'ADD'}]}, input_type=dict]
  • Memory data is successfully stored but tool appears to fail due to response format mismatch

Technical Root Cause

  • File: /usr/src/openmemory/app/mcp_server.py
  • Function: add_memories (line ~137)
  • Issue: Function returns raw dictionary object instead of string-serialized response
  • MCP protocol expects string return type for tool responses, not complex objects

Architecture Context

Claude Desktop → Supergateway → OpenMemory MCP Server → Memory Client
                     ↑
              Protocol validation error occurs here

Current Problematic Code

Location

  • File: /usr/src/openmemory/app/mcp_server.py
  • Function: add_memories
  • Line: Approximately 137

Current Implementation

@mcp.tool(description="Add a new memory...")
async def add_memories(text: str) -> str:
    # ... processing logic ...
    
    response = memory_client.add(text,
                               user_id=uid,
                               metadata={
                                  "source_app": "openmemory",
                                  "mcp_client": client_name,
                              })
    
    # Process response and update database
    if isinstance(response, dict) and 'results' in response:
        # ... database operations ...
        
    return response  # ← PROBLEM: Returns dict instead of string

Response Object Structure

The memory_client.add() method returns a dictionary with this structure:

{
  "results": [
    {
      "id": "549e4d2a-8b2f-4c7e-9a1d-3f5b8c2e4a7d",
      "memory": "User's memory text content",
      "event": "ADD"
    }
  ]
}

Solution Implementation

Required Change

Replace the return statement in the add_memories function to serialize the response as a JSON string.

Updated Code

@mcp.tool(description="Add a new memory...")
async def add_memories(text: str) -> str:
    # ... existing processing logic remains unchanged ...
    
    response = memory_client.add(text,
                               user_id=uid,
                               metadata={
                                  "source_app": "openmemory",
                                  "mcp_client": client_name,
                              })
    
    # Process response and update database
    if isinstance(response, dict) and 'results' in response:
        # ... existing database operations remain unchanged ...
        
    # Convert response dict to string for MCP protocol compatibility
    if isinstance(response, dict):
        return json.dumps(response)
    return str(response)

Import Requirements

Ensure the json module is imported at the top of the file:

import json

(Note: This import already exists in the current file)

Implementation Steps

Step 1: Locate the File

# In the OpenMemory repository root
ls app/mcp_server.py

Step 2: Identify the Function

Search for the add_memories function:

grep -n "async def add_memories" app/mcp_server.py

Step 3: Find the Return Statement

Look for line containing return response within the add_memories function (approximately line 137).

Step 4: Apply the Fix

Replace:

return response

With:

# Convert response dict to string for MCP protocol compatibility
if isinstance(response, dict):
    return json.dumps(response)
return str(response)

Step 5: Verify JSON Import

Confirm import json exists at the top of the file (it should already be present).

Testing Instructions

Unit Test Case

import json
import pytest
from unittest.mock import AsyncMock, MagicMock

async def test_add_memories_returns_string():
    """Test that add_memories returns a JSON string, not a dict."""
    
    # Mock the memory client response
    mock_response = {
        "results": [
            {
                "id": "test-id-123",
                "memory": "Test memory content",
                "event": "ADD"
            }
        ]
    }
    
    # Mock dependencies
    mock_memory_client = MagicMock()
    mock_memory_client.add.return_value = mock_response
    
    # Call the function (assuming proper mocking of context vars and DB)
    result = await add_memories("Test memory text")
    
    # Verify result is a string
    assert isinstance(result, str)
    
    # Verify result can be parsed as JSON
    parsed = json.loads(result)
    assert parsed == mock_response
    
    # Verify structure
    assert "results" in parsed
    assert len(parsed["results"]) == 1
    assert parsed["results"][0]["event"] == "ADD"

Integration Test

  1. Deploy the fix to a test environment
  2. Configure Claude Desktop with OpenMemory MCP via Supergateway
  3. Execute add_memories tool through Claude Desktop
  4. Verify no validation errors occur
  5. Confirm memory is successfully stored in the database

Docker Testing

If testing with Docker:

# Build image with fix
docker build -t openmemory-fixed .

# Run with proper port mapping
docker run -p 8765:8765 openmemory-fixed

# Test endpoint
curl -X GET "http://localhost:8765/mcp/claude/sse/testuser"

Validation Checklist

  • json module is imported
  • add_memories function signature unchanged: async def add_memories(text: str) -> str
  • Function still processes and stores memories correctly
  • Return value is always a string (JSON serialized or string converted)
  • No existing functionality is broken
  • Error handling remains intact
  • Database operations continue to work
  • MCP protocol compliance is achieved

Impact Assessment

Positive Impact

  • ✅ Fixes validation errors in Claude Desktop
  • ✅ Maintains backward compatibility
  • ✅ Improves MCP protocol compliance
  • ✅ No functional changes to memory storage
  • ✅ Minimal code change with low risk

Potential Risks

  • ⚠️ Clients expecting raw dict objects may need updates (unlikely as MCP specifies string responses)
  • ⚠️ JSON serialization adds minimal computational overhead
  • ⚠️ Requires testing across all MCP client implementations

Breaking Changes

  • None - this is a bug fix that makes the implementation compliant with the MCP specification

Repository Information

Target Repository

  • GitHub: https://github.com/mem0ai/mem0
  • Directory: openmemory/app/
  • File: mcp_server.py

Suggested Pull Request

  • Title: "Fix MCP protocol compliance: Return JSON string from add_memories tool"
  • Branch: fix/mcp-response-format
  • Labels: bug, mcp, protocol-compliance

Commit Message Template

fix(mcp): return JSON string from add_memories tool

- Convert dict response to JSON string for MCP protocol compliance
- Fixes Pydantic validation error in Claude Desktop integration
- Maintains all existing functionality and error handling
- Addresses issue where tool responses violated MCP string requirement

Closes #[ISSUE_NUMBER]

Additional Notes

MCP Protocol Reference

According to the Model Context Protocol specification, tool responses should return string values. Complex objects should be JSON-serialized when returned from MCP tools.

Alternative Solutions Considered

  1. Modify Supergateway: Would require changes to external dependency
  2. Change MCP Client: Would affect all users, not just this tool
  3. Update Protocol Specification: Not practical for this specific case

The chosen solution (JSON serialization) is the most appropriate as it:

  • Fixes the immediate problem
  • Maintains compatibility
  • Follows MCP best practices
  • Requires minimal code changes

Future Improvements

Consider implementing similar fixes for other tools in the MCP server that may have the same issue:

  • search_memory function
  • list_memories function (if it exists)
  • Any other tools returning complex objects

Contact Information

This fix was identified and tested by analyzing the integration between:

  • OpenMemory MCP Server v[current]
  • Supergateway v3.4.0
  • Claude Desktop on macOS
  • Docker containers: mem0/openmemory-mcp

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions