Labsco
elvismdev logo

mem0-mcp-selfhosted

β˜… 96

from elvismdev

Self-hosted mem0 MCP server for Claude Code. Run a complete memory server against self-hosted Qdrant + Neo4j + Ollama while using Claude as the main LLM.

πŸ”₯πŸ”₯πŸ”₯πŸ”₯βœ“ VerifiedFreeAdvanced setup

mem0-mcp-selfhosted

<a href="https://glama.ai/mcp/servers/elvismdev/mem0-mcp-selfhosted"><img width="380" height="200" src="https://glama.ai/mcp/servers/elvismdev/mem0-mcp-selfhosted/badge?v=1" alt="mem0-mcp-selfhosted MCP server" /></a>

Self-hosted mem0 MCP server for Claude Code. Run a complete memory server against self-hosted Qdrant + Neo4j + Ollama, with your choice of Anthropic (Claude) or Ollama as the main LLM.

Uses the mem0ai package directly as a library, supports both Claude's OAT token and fully local Ollama setups, and exposes 11 MCP tools for full memory management.

CLAUDE.md Integration

Add these rules to your project's CLAUDE.md (or ~/.claude/CLAUDE.md for global use) so Claude Code proactively uses memory tools throughout the session:

Copy & paste β€” that's it
# MCP Servers

- **mem0**: Persistent memory across sessions. At the start of each session, `search_memories` for relevant context before asking the user to re-explain anything. Use `add_memory` whenever you discover project architecture, coding conventions, debugging insights, key decisions, or user preferences. Use `update_memory` when prior context changes. Save information like: "This project uses PostgreSQL with Prisma", "Tests run with pytest -v", "Auth uses JWT validated in middleware". When in doubt, save it, future sessions benefit from over-remembering.

This gives Claude Code behavioral instructions to actively search and save memories during the session. For best results, combine with Claude Code Hooks, the CLAUDE.md rules tell Claude how to use memory tools mid-session, while hooks handle the automatic injection and saving at session boundaries.

Claude Code Hooks

Session hooks automate memory at session boundaries, injecting memories on startup and saving summaries on exit. This happens automatically without manual tool calls.

HookEventWhat it does
mem0-hook-contextSessionStart (startup, compact)Searches mem0 for project-relevant memories and injects them as additionalContext
mem0-hook-stopStopReads the last ~3 user/assistant exchanges from the transcript and saves a summary to mem0 via infer=True

Both hooks are non-fatal, if mem0 is unreachable or any error occurs, Claude Code continues normally.

Install

Install hooks into your project:

Copy & paste β€” that's it
mem0-install-hooks

Or install globally (all projects):

Copy & paste β€” that's it
mem0-install-hooks --global

This adds the hook entries to .claude/settings.json. The installer is idempotent, running it twice won't create duplicates.

How it works

On session start, the context hook searches mem0 with two queries (project architecture + recent session summaries), deduplicates by memory ID, and formats the results as numbered lines under a # mem0 Cross-Session Memory header. These are injected via the hook's additionalContext response field.

On session stop, the stop hook reads the JSONL transcript, extracts the last 6 user/assistant messages (a sliding window via bounded deque), builds a summary prompt, and calls memory.add(infer=True) to extract atomic facts. Graph is force-disabled in hooks to stay within the 15s/30s timeout budgets.

Entry points

CommandFunctionRegistered in pyproject.toml
mem0-hook-contexthooks:context_mainSessionStart hook
mem0-hook-stophooks:stop_mainStop hook
mem0-install-hookshooks:install_mainCLI installer

Hooks + CLAUDE.md

Hooks and CLAUDE.md are complementary layers that work best together:

LayerRoleWhen
HooksAutomated data flow, injects stored memories on startup, saves session summaries on exitSession boundaries (start/stop)
CLAUDE.mdBehavioral instructions, tells Claude to actively search and save memories during the sessionThroughout the session

Hooks alone give you passive recall (memories appear at startup) and passive saving (summaries saved at exit). CLAUDE.md instructions add active mid-session behavior, Claude searches for relevant memories when encountering new topics, and saves important discoveries immediately rather than waiting for session end.

For the best experience, use both. Hooks ensure memories flow in and out automatically at session boundaries, while CLAUDE.md ensures Claude actively engages with memory tools during the session.

Authentication

The server resolves an Anthropic token using a prioritized fallback chain:

PrioritySourceDetails
1MEM0_ANTHROPIC_TOKEN env varExplicit, user-controlled
2~/.claude/.credentials.jsonAuto-reads Claude Code's OAT token (zero-config)
3ANTHROPIC_API_KEY env varStandard pay-per-use API key
4DisabledWarns and disables Anthropic LLM features

In Claude Code, priority 2 always wins, the credentials file exists as long as you're logged in. This means ANTHROPIC_API_KEY (priority 3) is never reached. To override the OAT token in Claude Code, use MEM0_ANTHROPIC_TOKEN (priority 1). ANTHROPIC_API_KEY is only useful for non-Claude-Code deployments (Docker, CI, standalone).

OAT tokens (sk-ant-oat...) use your Claude subscription. The server automatically detects the token type and configures the SDK accordingly. OAT tokens are automatically refreshed before expiry: the server proactively checks the token lifetime and refreshes via the Anthropic OAuth endpoint when nearing expiry (default: 30 minutes). On authentication failures, a 3-step defensive strategy kicks in, piggybacking on Claude Code's credentials file, self-refreshing via OAuth, and wait-and-retry, so long-running sessions survive token rotation seamlessly.

API keys (sk-ant-api...) use standard pay-per-use billing.

Tools

Memory Tools (9 core)

ToolDescription
add_memoryStore text or conversation history as memories. Supports enable_graph, infer, metadata.
search_memoriesSemantic search with optional filters, threshold, rerank, enable_graph.
get_memoriesList/filter memories (non-search). Supports limit and scope filters.
get_memoryFetch a single memory by UUID.
update_memoryReplace memory text. Re-embeds and re-indexes in Qdrant.
delete_memoryDelete a single memory by UUID.
delete_all_memoriesBulk-delete all memories in a scope.
list_entitiesList users/agents/runs with memory counts. Uses Qdrant Facet API.
delete_entitiesCascade-delete an entity and all its memories.

Graph Tools

ToolDescription
search_graphSearch Neo4j entities by name substring. Returns entities + outgoing relationships.
get_entityGet all relationships for an entity (bidirectional: incoming + outgoing).

Prompt

The server registers a memory_assistant MCP prompt that provides Claude with a quick-start guide for using the memory tools effectively.

Parameters

All tools use Pydantic Annotated[type, Field(description=...)] for self-documenting parameter schemas. Common patterns:

  • user_id defaults to MEM0_USER_ID env var when not provided
  • enable_graph overrides the default MEM0_ENABLE_GRAPH per-call
  • filters supports structured operators: {"key": {"eq": "value"}}, {"AND": [...]}
  • All responses are JSON strings via json.dumps(result, ensure_ascii=False)

Architecture

Copy & paste β€” that's it
Claude Code
  |
  β”œβ”€β”€ MCP stdio/SSE/streamable-http
  β”‚     |
  β”‚     β”œβ”€β”€ env.py               ← Centralized env var readers (whitespace-safe)
  β”‚     β”œβ”€β”€ auth.py              ← Hybrid token fallback chain + OAT self-refresh
  β”‚     β”œβ”€β”€ llm_anthropic.py     ← Custom Anthropic LLM provider (OAT + structured outputs)
  β”‚     β”œβ”€β”€ llm_ollama.py        ← Custom Ollama LLM provider (restored tool-calling)
  β”‚     β”œβ”€β”€ config.py            ← Env vars β†’ MemoryConfig dict (provider + URL cascades)
  β”‚     β”œβ”€β”€ helpers.py           ← Error wrapper, concurrency lock, safe bulk-delete, monkey-patches
  β”‚     β”œβ”€β”€ graph_tools.py       ← Direct Neo4j Cypher queries (lazy driver)
  β”‚     β”œβ”€β”€ llm_router.py        ← Split-model graph LLM router (gemini_split)
  β”‚     β”œβ”€β”€ __init__.py          ← Telemetry suppression (before any mem0 import)
  β”‚     └── server.py            ← FastMCP orchestrator (11 tools + prompt)
  β”‚           |
  β”‚           β”œβ”€β”€ mem0ai Memory class
  β”‚           β”‚     β”œβ”€β”€ Vector: LLM fact extraction β†’ Ollama embed β†’ Qdrant
  β”‚           β”‚     └── Graph: LLM entity extraction (tool calls) β†’ Neo4j
  β”‚           |
  β”‚           └── Infrastructure
  β”‚                 β”œβ”€β”€ Qdrant          ← Vector store
  β”‚                 β”œβ”€β”€ Ollama          ← Embeddings
  β”‚                 β”œβ”€β”€ Neo4j           ← Knowledge graph (optional)
  β”‚                 └── Anthropic/Ollama ← Main LLM (configurable)
  |
  └── Session Hooks (subprocess, not MCP)
        |
        └── hooks.py             ← Cross-session memory (SessionStart + Stop hooks)
              β”œβ”€β”€ context_main()   β†’ Injects memories as additionalContext on startup/compact
              β”œβ”€β”€ stop_main()      β†’ Saves session summary to mem0 on exit
              └── install_main()   β†’ CLI to patch .claude/settings.json

Graph Memory & Quota

Graph memory is disabled by default (MEM0_ENABLE_GRAPH=false) to protect your Claude quota. Each add_memory with graph enabled triggers 3 additional LLM calls for entity extraction, relationship generation, and conflict resolution.

Using Ollama for Graph Operations

To eliminate Claude quota usage for graph ops, use a local Ollama model:

Copy & paste β€” that's it
MEM0_ENABLE_GRAPH=true
MEM0_GRAPH_LLM_PROVIDER=ollama
MEM0_GRAPH_LLM_MODEL=qwen3:14b

Qwen3:14b has 0.971 tool-calling F1 (nearly matching GPT-4's 0.974) and runs in ~7-8GB VRAM with Q4_K_M quantization.

Using Gemini for Graph Operations

Google's Gemini 2.5 Flash Lite is the cheapest option for graph ops while maintaining strong entity extraction accuracy:

Copy & paste β€” that's it
MEM0_ENABLE_GRAPH=true
MEM0_GRAPH_LLM_PROVIDER=gemini
MEM0_GRAPH_LLM_MODEL=gemini-2.5-flash-lite
GOOGLE_API_KEY=your-google-api-key

Using Split-Model for Best Accuracy

The gemini_split provider routes graph pipeline calls to different LLMs based on the operation. Entity extraction (Calls 1 & 2) goes to Gemini for speed and cost; contradiction detection (Call 3) goes to Claude for accuracy.

Copy & paste β€” that's it
MEM0_ENABLE_GRAPH=true
MEM0_GRAPH_LLM_PROVIDER=gemini_split
GOOGLE_API_KEY=your-google-api-key
MEM0_GRAPH_CONTRADICTION_LLM_PROVIDER=anthropic
MEM0_GRAPH_CONTRADICTION_LLM_MODEL=claude-opus-4-6

Benchmark results across 248 test cases: Gemini scores 85.4% on entity extraction (vs Claude's 79.1%), while Claude scores 100% on contradiction detection (vs Gemini's 80%). The split-model combines the best of both.

Transport Modes

ModeUse CaseConfig
stdio (default)Claude Code integrationMEM0_TRANSPORT=stdio
sseLegacy remote clientsMEM0_TRANSPORT=sse
streamable-httpModern remote clientsMEM0_TRANSPORT=streamable-http

For remote deployments, MCP SDK >= 1.23.0 enables DNS rebinding protection by default.

Development

Copy & paste β€” that's it
# Install with dev dependencies
pip install -e ".[dev]"

# Run unit tests
python3 -m pytest tests/unit/ -v

# Run contract tests (validates mem0ai internal API assumptions)
python3 -m pytest tests/contract/ -v

# Run integration tests (requires live Qdrant + Neo4j + Ollama)
python3 -m pytest tests/integration/ -v

# Run all tests
python3 -m pytest tests/ -v

Test Structure

  • tests/unit/ -- Pure unit tests with mocked dependencies (env, auth, config, config matrix, concurrency, MCP protocol, helpers, hooks, LLM providers, graph tools, LLM router, server)
  • tests/contract/ -- Validates assumptions about mem0ai internals (schema detection invariant, vector_store.client access path, LlmFactory registration idempotency)
  • tests/integration/ -- Live infrastructure tests (memory lifecycle, graph ops, bulk operations, hooks) against real Qdrant + Neo4j + Ollama. Marked with @pytest.mark.integration.

Contract tests catch breaking changes in mem0ai upgrades before they reach production.

Telemetry

All mem0ai telemetry is suppressed. os.environ["MEM0_TELEMETRY"] = "false" is set at package import time, before any mem0 module is loaded. No PostHog events are sent.

License

MIT