Featured

Deploy OpenClaw in 60 seconds — 20% off logoDeploy OpenClaw in 60 seconds — 20% off

Launch OpenClaw on Hostinger in about 60 seconds and keep your agent live 24/7. Our referral link gives you 20% off, no coupon code needed.

Launch on Hostinger
Run your Hermes agent on Hostinger, fully managed logoRun your Hermes agent on Hostinger, fully managed

Launch Hermes on Hostinger in one click, fully managed, no VPS knowledge needed. Use code ZACAARON10 for 10% off.

Launch on Hostinger
Crawl and scrape any site into clean data, 10% off logoCrawl and scrape any site into clean data, 10% off

Firecrawl crawls and scrapes any site into clean markdown for your agent. Get 1,000 free credits, and new users get 10% off their first purchase.

Try Firecrawl free
6,000+ web scrapers for your AI agent, start free logo6,000+ web scrapers for your AI agent, start free

Apify gives your agent live web data: 6,000+ prebuilt scrapers and actors, MCP-ready. Sign up free with $5 in usage credits.

Try Apify free
One API to scrape, enrich, and extract the internet. logoOne API to scrape, enrich, and extract the internet.

Context.dev gives your agents a single API to scrape, enrich, and extract live web data — no proxies, no parsers, no maintenance.

Start building free
SetupClaw: done-for-you OpenClaw for founders & exec teams logoSetupClaw: done-for-you OpenClaw for founders & exec teams

White-glove OpenClaw for founders and exec teams (4–50+ employees): we install, harden, integrate your tools, and maintain it — secured from day one.

Get it set up for you
SEO data APIs for your agent, $1 free credit logoSEO data APIs for your agent, $1 free credit

DataForSEO gives your agent live access to SERP results, keyword data, backlinks, and on-page SEO data through one API. New accounts get a $1 credit, good for up to 20,000 keyword or backlink lookups.

Try DataForSEO free
Reach 48,000+ AI builders

A flat monthly placement in front of developers actively installing AI tools. No lock-in, cancel anytime.

Advertise here

Works with

Claude CodeClaude DesktopCursorVS CodeClineCodex CLIOpenClaw+ any MCP client

Install to Claude Code

This server doesn't publish a one-line install command. Follow the setup in the source repository.

Summary

A lightweight memory layer for MCP servers that persists user context across LLM sessions with minimal token cost, supporting key-value dedup, multi-tenant namespaces, and optional TTL.

README.md

MCP Persistent Context

A lightweight memory layer for custom MCP servers — persist user context across LLM sessions with minimal token cost.

4 tools. ~600 schema tokens. Key-value dedup. Multi-tenant. Cross-MCP.

Why?

LLMs forget everything between sessions. Every conversation starts from zero.

The official @modelcontextprotocol/server-memory solves this with a knowledge graph (entities, relations, observations). It's powerful — but can be overkill for simple context persistence:

| | server-memory | This project | |---|---|---| | Tools | 9 (~1500 schema tokens) | 4 (~600 schema tokens) | | Read cost | read_graph returns full graph | Paginated, filtered, compact | | Multi-tenant | No | Yes (client_id) | | Cross-MCP | No | Yes (namespace) | | Dedup | By entity name | By key within (client_id, namespace, category) | | TTL | No | Optional per-entry expiration |

They are complementary. Use server-memory when you need entity relationships. Use this when you need fast, cheap, structured context for custom MCP projects.

Which version should I use?

Do you have 1 MCP server or multiple?

  1 MCP server ──→ Embed (tools_memory.py)
                    Copy into your project, register tools, done.

  2+ MCP servers ─→ Standalone (mcp_memory_server.py)
                    Run as a separate MCP. Domain servers stay clean.
                    Context is shared across all MCPs.

| | Embedded | Standalone | |---|---|---| | File | tools_memory.py | mcp_memory_server.py | | Setup | Import + register in your server | Run as separate process | | Cross-MCP | No (lives inside one MCP) | Yes (shared by all MCPs) | | Schema cost | Adds ~600 tokens to your MCP | ~600 tokens in its own MCP | | Best for | Single MCP projects | Multi-MCP architectures |

Quick Start

Standalone server

pip install "mcp[cli]"
python mcp_memory_server.py --transport streamable-http --port 8770

For Claude Desktop (stdio):

{
  "mcpServers": {
    "memory": {
      "command": "python",
      "args": ["path/to/mcp_memory_server.py"],
      "env": {
        "MEMORY_DIR": "/path/to/memory_data",
        "MAX_ENTRIES_PER_CLIENT": "500"
      }
    }
  }
}

Embedded in your MCP server

from mcp.server.fastmcp import FastMCP
from tools_memory import register_memory_tools
from pathlib import Path
from datetime import datetime

mcp = FastMCP("My App")

# Register your domain tools
@mcp.tool()
def do_something(query: str) -> str:
    return process(query)

# Register memory tools (4 tools added to your server)
register_memory_tools(
    mcp,
    memory_dir=Path("./memory_data"),
)

mcp.run()

Tools

save_memory

save_memory(
    category="business_context",
    type="insight",
    content="AS=0 | persona=seniors | monetization=affiliate",
    reason="Client business context for SEO strategy.",
    client_id="_default",
    namespace="general",
    ttl_days=0
)
→ "Saved. business_context | insight | 4 entries"

get_memory

get_memory(client_id="acme_corp")
→ Memory 'acme_corp' (4/4):
  2026-02-26 INSIGHT | general/business_context | AS=0 | persona=seniors | monetization=affiliate
  2026-02-26 DECISION | seo/domain_context | pillar=cloud_computing | approach=editorial_first
  2026-02-26 EXCLUSION | seo/domain_context | exclude=serverless | reason=off_topic
  2026-02-15 ACTION | general/project_config | stack=React+Node | deploy=Vercel [90d]

delete_memory

delete_memory(content_match="persona", client_id="acme_corp")
→ "Deleted: AS=0 | persona=seniors | monetization=affiliate
   3 entries remaining"

memory_status

memory_status(client_id="acme_corp")
→ "'acme_corp': 3 entries | ns: general, seo | cat: business_context, domain_context | 2026-02-15 → 2026-02-26 | 1 with TTL"

Content Format

key=value | key=value | key=value

Why key=value, not JSON?

  • 2-3x fewer tokens ({"key":"value"} = 7 tokens, key=value = 3)
  • Enables key-based dedup without NLP
  • LLMs naturally produce and parse it
  • Works across any domain

Key=value is recommended, not enforced. The server warns if no = is detected, but still saves the entry. Some use cases need free text (e.g. content="Client confirmed budget by phone"). The dedup engine simply skips entries without parseable keys.

Examples across domains:

# Marketing / SEO
"AS=0 | persona=seniors | monetization=affiliate+partnerships"

# Healthcare
"allergy=penicillin | blood_type=O+ | primary_care=Dr.Smith"

# Software Engineering
"stack=React+Node | deploy=Vercel | CI=GitHub_Actions"

# Legal
"jurisdiction=FR | entity=SAS | fiscal_year=calendar"

# Education
"level=grade10 | learning_style=visual | weakness=algebra"

Key-Based Dedup

Same (client_id, namespace, category) + overlapping key → merge, don't duplicate:

Existing:  "AS=0 | persona=seniors"
Incoming:  "AS=12 | site=launched"
Result:    "AS=12 | persona=seniors | site=launched"

No parseable keys → append as new entry (no dedup attempted).

Integration Examples

Example 1: Domain MCP delegates memory to standalone server

Your domain MCP does its job. Memory lives elsewhere.

# my_domain_mcp.py — zero memory logic
@mcp.tool()
def analyze_data(query: str) -> str:
    results = run_analysis(query)
    return json.dumps(results)

Claude's system prompt handles the memory calls:

You have access to two MCP servers: Domain and Memory.
At session start: call get_memory() to load user context.
When the user shares business context, preferences, or decisions:
  call save_memory() with key=value content.

Claude sees both MCPs, calls get_memory() at start, calls domain tools for work, calls save_memory() when the user shares context. The domain MCP never touches memory.

Example 2: Single MCP with embedded memory

# my_mcp_server.py
from mcp.server.fastmcp import FastMCP
from tools_memory import register_memory_tools

mcp = FastMCP("My App")

@mcp.tool()
def do_something(query: str) -> str:
    result = process(query)
    # Trigger reminder in response
    return f"{result}\n\nMEMORY: save_memory() if user shared context."

register_memory_tools(mcp, memory_dir=Path("./data"))
mcp.run()

Example 3: Multi-tenant with namespace filtering

# User works with client "acme_corp" across multiple domains

# Session 1 (SEO context)
save_memory(client_id="acme_corp", namespace="seo",
            category="business_context", type="insight",
            content="AS=45 | market=US | vertical=saas",
            reason="SEO baseline metrics")

# Session 2 (Ads context) — can read SEO memory too
get_memory(client_id="acme_corp")
# → returns BOTH seo and ads entries

get_memory(client_id="acme_corp", namespace="ads")
# → returns only ads entries

Categories

Recommended (cover most domains):

| Category | What it stores | |----------|---------------| | business_context | Company, market, monetization, personas | | project_config | Stack, architecture, conventions | | user_preference | Workflow, tone, formatting style | | domain_context | Domain-specific decisions | | analysis_context | Recurring findings, baselines | | content_strategy | Editorial guidelines, content types |

Custom: Use x_ prefix (x_medical_history, x_legal_discovery). The server warns on unknown categories but does not reject them.

Types

| Type | When to use | |------|-------------| | decision | User chose between options | | exclusion | User explicitly rejected something | | insight | Factual context about user/project | | action | User committed to a plan | | anomaly | Unexpected finding worth remembering |

TTL (Time-To-Live)

save_memory(..., ttl_days=90)  # expires in 90 days
save_memory(..., ttl_days=0)   # permanent (default)
  • Permanent: Business identity, user preferences, architecture decisions
  • 90 days: Campaign context, quarterly goals
  • 30 days: Temporary constraints, short-term priorities

Expired entries are pruned automatically on get_memory.

Architecture: 1 Memory MCP, N Domain MCPs

┌─────────────┐   ┌─────────────┐   ┌─────────────┐
│  MCP SEO    │   │  MCP Ads    │   │  MCP Email  │
│  0 memory   │   │  0 memory   │   │  0 memory   │
│  tools      │   │  tools      │   │  tools      │
└──────┬──────┘   └──────┬──────┘   └──────┬──────┘
       │                 │                 │
       └────────┬────────┴────────┬────────┘
                │                 │
         ┌──────┴──────┐         │
         │ MCP Memory  │◄────────┘
         │ 4 tools     │
         │ shared ctx  │
         │ ~600 tok    │
         └─────────────┘

Benefits:

  • Schema tokens: ~600 total (not ~600 x N)
  • 1 get_memory at session start (not N)
  • Context from SEO visible in Ads and vice versa
  • Domain MCPs stay focused on their job

Triggering Memory Calls

System prompt instructions alone do NOT reliably trigger LLM memory calls.

Strategy A — Dedicated Memory MCP (recommended for multi-MCP):

Add to system prompt: `` At session start: call get_memory() to load user context. After state-changing tools: if the user shared context, call save_memory(). ``

Strategy B — Embedded in domain MCP (for single-MCP setups):

Inject short reminders in tool responses: `` MEMORY: context shared? → save_memory() | correction? → delete_memory() ``

Keep trigger text under 25 tokens per tool response.

What to persist

| Persist | Don't persist | |---------|--------------| | User decisions and preferences | Tool outputs or raw data | | Business constraints | Intermediate calculations | | Explicit corrections | Session-specific state | | What changes future behavior | What can be re-derived |

Server-Side Guards

| Guard | Rule | |-------|------| | Key dedup | Same (client_id, ns, category) + overlapping key → merge | | Truncate | content capped at 500 chars | | Prune | Max entries per client (default: 200, configurable) | | TTL | Expired entries pruned on read | | Content warning | Soft warn if no = detected (does not reject) | | Category warning | Soft warn on non-standard categories (does not reject) |

Configuration

| Variable | Default | Description | |----------|---------|-------------| | MEMORY_DIR | ./memory_data | Base directory for memory files | | MEMORY_PORT | 8770 | HTTP port (streamable-http transport) | | MAX_CONTENT_LEN | 500 | Max characters per content field | | MAX_ENTRIES_PER_CLIENT | 200 | Max entries per client before pruning oldest |

Storage

{MEMORY_DIR}/{client_id}/memory.json

Each entry: ``json { "namespace": "seo", "category": "business_context", "type": "insight", "content": "AS=0 | persona=seniors | monetization=affiliate", "reason": "Client business profile.", "date": "2026-02-26T14:30:00", "ttl_days": 90 } ``

Implementation Checklist

  • [ ] 4 tools: save_memory, get_memory, delete_memory, memory_status
  • [ ] content format: key=value | key=value (soft warn if no =)
  • [ ] Key-based dedup on (client_id, namespace, category)
  • [ ] content truncated at MAX_CONTENT_LEN (default 500)
  • [ ] Max MAX_ENTRIES_PER_CLIENT entries (default 200)
  • [ ] TTL pruning on get_memory
  • [ ] client_id defaults to _default
  • [ ] namespace defaults to general
  • [ ] Categories: recommended set + custom x_ prefix (warn, don't reject)
  • [ ] type enum: decision, exclusion, insight, action, anomaly

License

MIT

See related servers & alternatives →

Related MCP servers

Browse all →

Related guides

Hand-picked reading to help you choose and use Vector & Memory servers.