How to Debug Memory Storage Issues Using Health Check Endpoints in MCP Memory Service

Query the /api/health/detailed endpoint to inspect storage backend statistics, memory counts, and database size, or use /api/health/sync-status for hybrid backend synchronization progress to quickly identify the root cause of memory storage failures.

The MCP Memory Service (doobidoo/mcp-memory-service) exposes HTTP health check endpoints that surface real-time storage backend statistics and system metrics. By analyzing these JSON payloads, you can debug memory storage issues without accessing the database directly or parsing application logs.

Health Check Endpoint Overview

Three endpoints in src/mcp_memory_service/web/api/health.py provide progressive levels of diagnostic detail:

  • /api/health — Returns minimal liveness status, version, and uptime
  • /api/health/detailed — Returns full storage statistics, system metrics, and performance data
  • /api/health/sync-status — Returns hybrid backend synchronization progress

Both basic and detailed endpoints delegate storage health checks to MemoryService.health_check(), implemented in src/mcp_memory_service/services/memory_service.py at lines 690-707, which calls storage.get_stats() on the configured backend.

Decoding the Detailed Health Payload

The /api/health/detailed endpoint returns a comprehensive JSON object containing storage metadata critical for debugging.

Storage Backend Statistics

The storage object reveals the backend type and data integrity:

{
  "backend": "sqlite-vec",
  "total_memories": 1247,
  "unique_tags": 87,
  "memories_this_week": 23,
  "database_size_mb": 12.34,
  "embedding_model": "all-MiniLM-L6-v2",
  "embedding_dimension": 384
}
  • backend: Identifies the implementation (sqlite-vec, hybrid, cloudflare) from storage.get_stats() in src/mcp_memory_service/storage/sqlite_vec.py (lines 2581-2619)
  • total_memories: Count of non-soft-deleted rows via SELECT COUNT(*) ... WHERE deleted_at IS NULL
  • unique_tags: Distinct tag count; low values indicate normalize_tags issues in src/mcp_memory_service/services/memory_service.py (lines 55-75)
  • database_size_mb: Physical SQLite file size from os.path.getsize(self.db_path); sudden drops indicate corruption or resets

Hybrid Sync Status

For hybrid backends, the /api/health/sync-status endpoint (lines 95-110 in health.py) queries get_initial_sync_status() implemented in src/mcp_memory_service/storage/hybrid.py (lines 1516-1525). This reveals whether background synchronization is stuck:

{
  "sync_supported": true,
  "status": {
    "in_progress": true,
    "pending_operations": 42,
    "progress_percentage": 65
  }
}

Step-by-Step Debugging Workflow

Follow this sequence to isolate memory storage issues using the health endpoints:

  1. Verify Service Liveness

    curl -s http://localhost:8000/api/health | jq .

    If status is not "healthy", check the service logs for startup failures in dependency injection (src/mcp_memory_service/web/dependencies.py).

  2. Inspect Storage Metrics

    curl -s http://localhost:8000/api/health/detailed | jq .storage

    Confirm the backend matches your configuration and total_memories reflects expected data volume.

  3. Validate Database Growth

    Monitor database_size_mb across requests. Static values during active writes indicate permission failures or storage backend disconnections.

  4. Check Tag Integrity

    If unique_tags is zero despite memories existing, inspect the normalize_tags function in memory_service.py for comma-handling bugs.

  5. Monitor Hybrid Synchronization

    curl -s http://localhost:8000/api/health/sync-status | jq .

    Persistent "in_progress": true with high pending_operations indicates network latency or authentication failures in the hybrid sync worker.

Diagnosing Common Storage Issues

Map symptoms to health payload indicators:

Service Startup Failures

If /api/health returns 500 or missing status field, the MemoryService failed to initialize. Check that the storage backend implements the get_stats() method declared in src/mcp_memory_service/storage/base.py (lines 763-770).

Silent Write Failures

When total_memories remains unchanged after API writes, verify database_size_mb growth. If both are static, the SQLite file lacks write permissions or the storage.store() method is failing silently.

Tag Search Anomalies

Low unique_tags with high total_memories signals that the tag normalization logic is stripping delimiters incorrectly. Compare the health output against direct storage queries:

from mcp_memory_service.storage.sqlite_vec import SqliteVectorStore

store = SqliteVectorStore(db_path="data/memory.db")
await store.initialize()
print(await store.get_stats())

Disk Space Exhaustion

When system.disk_percent exceeds 90% or database_size_mb approaches system.disk_free_gb, the host cannot allocate new pages. Enable database rotation or clean old memory segments.

Backend Mismatch

An backend value of "unknown" indicates the storage class does not properly implement get_stats(). Verify your configuration points to a valid MemoryStorage implementation.

Programmatic Health Check Examples

cURL Diagnostics for Storage Backend

Check storage statistics directly:

curl -s http://localhost:8000/api/health/detailed | jq .storage

Python Async Health Monitor

import httpx
import asyncio

async def check_storage_health():
    async with httpx.AsyncClient() as client:
        resp = await client.get("http://localhost:8000/api/health/detailed")
        data = resp.json()
        storage = data["storage"]
        
        assert storage["backend"] == "sqlite-vec"
        assert storage["total_memories"] > 0
        assert storage["database_size_mb"] > 0
        
        print(f"Backend: {storage['backend']}")
        print(f"Memories: {storage['total_memories']}")
        print(f"DB Size: {storage['database_size_mb']} MB")

asyncio.run(check_storage_health())

Direct Storage Inspection

Bypass HTTP to verify endpoint accuracy:

import asyncio
from mcp_memory_service.storage.sqlite_vec import SqliteVectorStore

async def verify_stats():
    store = SqliteVectorStore(db_path="data/memory.db")
    await store.initialize()
    stats = await store.get_stats()
    print(f"Direct storage query: {stats}")

asyncio.run(verify_stats())

Summary

  • /api/health confirms service process liveness and basic uptime
  • /api/health/detailed exposes storage backend type, memory counts, database file size, and embedding configuration from sqlite_vec.py and hybrid.py
  • /api/health/sync-status tracks background synchronization progress for hybrid deployments
  • Correlate total_memories, unique_tags, and database_size_mb against expected values to detect write failures or tag processing errors
  • Use system.disk_percent and memory_percent to identify resource exhaustion causing storage timeouts
  • Validate health endpoint output against direct storage.get_stats() calls to ensure the HTTP layer accurately reflects backend state

Frequently Asked Questions

Why does /api/health return healthy but my memories are missing?

The basic endpoint only checks process liveness. Query /api/health/detailed and inspect storage.total_memories. If the count is zero despite previous writes, the service may be pointing to a different database file path or the storage backend failed to initialize properly. Check storage.backend matches your configured implementation.

How do I detect if the hybrid storage sync is stuck?

Poll /api/health/sync-status and examine status.in_progress. If this remains true for more than 30 minutes with non-zero pending_operations, the background sync worker in src/mcp_memory_service/storage/hybrid.py is likely blocked by network latency or authentication failures. Review logs for sync-related exceptions.

What does a low unique_tags count indicate?

When unique_tags is unexpectedly low compared to total_memories, the normalize_tags function in src/mcp_memory_service/services/memory_service.py (lines 55-75) may be incorrectly stripping tag delimiters or converting tags to empty strings. Compare the health payload against a direct query of storage.get_stats() to confirm the discrepancy.

Why is database_size_mb not increasing when I add memories?

Static database size during write operations indicates the SQLite file is not being modified. Verify file permissions on the database path and ensure the disk has free space (system.disk_free_gb). If permissions and space are adequate, check for transaction rollbacks or silent failures in storage.store() implementations.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →