mcp-persistent-context

mcp-persistent-context

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.

Category
Visit Server

README

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:

{
  "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

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured