Doramagic Project Pack · Human Manual

agent-memory-mcp

Related topics: Installation, Quick Start Guide

Overview

Related topics: Installation, Quick Start Guide

Section Related Pages

Continue reading this section for the full explanation and source context.

Section Storage Model

Continue reading this section for the full explanation and source context.

Section Tool Handlers

Continue reading this section for the full explanation and source context.

Section Memory Entry Schema

Continue reading this section for the full explanation and source context.

Related topics: Installation, Quick Start Guide

Overview

Agent Memory MCP is a persistent key-value memory store designed specifically for AI agents. It solves the fundamental problem of context loss between sessions by providing a durable, searchable storage layer that survives restarts, crashes, and context-window limitations.

Purpose and Scope

AI agents typically lose all context between sessions. Every conversation starts from zero, requiring users to re-explain preferences, history, and requirements repeatedly. Agent Memory MCP addresses this by implementing a persistent memory system with the following capabilities:

  • Persistent Storage: Data survives agent restarts and system reboots
  • TTL Support: Auto-expiring memories with second-level precision
  • Namespace Isolation: Organize memories by project, user, or domain
  • Fuzzy Search: Case-insensitive keyword search across all namespaces
  • Access Tracking: Monitor memory usage patterns and entry activity

Sources: README.md

Architecture

Agent Memory MCP is built on the Model Context Protocol (MCP) Python SDK and operates as a stdio-based server. The architecture follows a simple but effective layered design:

graph TD
    subgraph "Client Layer"
        A[AI Agent / Claude Desktop]
    end
    
    subgraph "MCP Protocol Layer"
        B[MCP Server]
    end
    
    subgraph "Storage Layer"
        C[~/.agent-memory/]
        D[namespace1.json]
        E[namespace2.json]
        F[_meta.json]
    end
    
    A -->|MCP Protocol| B
    B -->|Read/Write| C
    C --> D
    C --> E
    C --> F

Sources: server.py

Storage Model

The system stores data as JSON files in ~/.agent-memory/, with one file per namespace plus a metadata file:

File PatternPurpose
{namespace}.jsonKey-value entries for a specific namespace
_meta.jsonGlobal statistics and metadata

Namespace filenames are sanitized to prevent directory traversal attacks. Characters outside [a-zA-Z0-9_.-] are replaced with underscores.

Sources: server.py:42-49

Core Components

Tool Handlers

The MCP server exposes seven tools for memory management:

ToolDescriptionRead-OnlyDestructive
memory_rememberStore a value with optional TTLNoNo
memory_recallRetrieve a value + metadataYesNo
memory_forgetDelete a key permanentlyNoYes
memory_searchSearch all namespaces by keywordYesNo
memory_list_namespacesList namespaces with countsYesNo
memory_clear_namespaceWipe a namespaceNoYes
memory_statsGlobal storage statisticsYesNo

Sources: server.py:206-298

Memory Entry Schema

Each memory entry contains the following fields:

FieldTypeDescription
keystringUnique identifier within namespace
valuestringStored content (max ~25,000 characters)
namespacestringStorage partition identifier
created_atISO timestampCreation timestamp
accessed_atISO timestampLast access timestamp
expires_atISO timestamp or nullTTL expiration time
access_countintegerNumber of times accessed

Sources: server.py:350-358

Concurrency and Safety

The system implements POSIX file locking via fcntl.flock to ensure thread-safe concurrent access from multiple agents:

@contextmanager
def _locked_file(path: Path, mode: str = "r+"):
    fh = open(path, mode)
    try:
        fcntl.flock(fh, fcntl.LOCK_EX)
        yield fh
    finally:
        fcntl.flock(fh, fcntl.LOCK_UN)
        fh.close()

The lock gracefully degrades on platforms without fcntl support (e.g., Windows without WSL).

Sources: server.py:67-83

Tier System

TierEntriesPriceFeatures
Free1,000FreeAll 7 tools
ProUnlimited$19/monthUnlimited storage

Sources: smithery.yaml

Installation

pip install agent-memory-mcp

Requirements:

  • Python 3.10+
  • mcp >= 1.0.0

Sources: requirements.txt

Entry Point

The server runs as an async stdio server:

async def main() -> None:
    _ensure_storage()
    async with stdio_server() as (read_stream, write_stream):
        await server.run(
            read_stream,
            write_stream,
            server.create_initialization_options(),
        )

Sources: server.py:318-326

License and Attribution

AttributeValue
LicenseMIT
AuthorNous Research
Repositorygithub.com/nousresearch/agent-memory-mcp

Sources: README.md

Sources: README.md

Installation

Related topics: Overview, Quick Start Guide

Section Related Pages

Continue reading this section for the full explanation and source context.

Section System Dependencies

Continue reading this section for the full explanation and source context.

Section Method 1: pip (Recommended)

Continue reading this section for the full explanation and source context.

Section Method 2: From Source

Continue reading this section for the full explanation and source context.

Related topics: Overview, Quick Start Guide

Installation

This guide covers all methods for installing and configuring the Agent Memory MCP server. The server provides persistent key-value memory storage for AI agents with TTL support, namespaces, fuzzy search, and access tracking.

Prerequisites

Before installing Agent Memory MCP, ensure your environment meets the following requirements:

RequirementVersionNotes
Python3.10+Core runtime requirement
pipLatestFor package installation
POSIX-compatible OSLinux/macOSRequired for file locking via fcntl
MCP ClientCompatible versionAny MCP 1.0.0+ compatible client

System Dependencies

The server relies on POSIX file locking (fcntl) for thread-safe concurrent access:

# From server.py:34-41
try:
    fcntl.flock(fh, fcntl.LOCK_EX)
except (NameError, OSError):
    pass  # platform without fcntl support

On Windows without WSL, file locking falls back gracefully as a no-op.

Sources: server.py:34-41

Installation Methods

The simplest installation method uses pip directly from PyPI:

pip install agent-memory-mcp

This command installs the package and its dependencies, including the MCP SDK.

Sources: index.html:1

Method 2: From Source

To install the latest development version from the repository:

# Clone the repository
git clone https://github.com/nousresearch/agent-memory-mcp.git
cd agent-memory-mcp

# Install in development mode
pip install -e .

# Or install production dependencies
pip install -r requirements.txt

Method 3: Using Smithery.ai

For deployment via Smithery.ai marketplace, the server is pre-configured:

# From smithery.yaml
runtime:
  type: python
  entrypoint: server.py
  requirements: requirements.txt

Sources: smithery.yaml:1-6

Dependencies

Agent Memory MCP has a minimal dependency footprint:

PackageVersionPurpose
mcp≥1.0.0Model Context Protocol SDK

Sources: requirements.txt:1

The MCP SDK provides:

  • Server implementation via mcp.server.Server
  • Stdio transport via mcp.server.stdio.stdio_server
  • Tool definitions via mcp.types

Sources: server.py:17-25

Server Entry Point

After installation, the server can be run directly:

python server.py

The server uses stdio transport for communication with MCP clients:

# From server.py:267-272
async def main() -> None:
    _ensure_storage()
    async with stdio_server() as (read_stream, write_stream):
        await server.run(
            read_stream,
            write_stream,
            server.create_initialization_options(),
        )

Sources: server.py:267-272

Storage Initialization

On first run, the server automatically creates the storage directory:

# From server.py:47-49
def _ensure_storage() -> None:
    """Create the storage directory if it doesn't exist."""
    STORAGE_DIR.mkdir(parents=True, exist_ok=True)

The default storage location is ~/.agent-memory/:

# From server.py:38
STORAGE_DIR = Path.home() / ".agent-memory"

Sources: server.py:38,47-49

Storage Structure

The storage directory contains one JSON file per namespace plus a metadata file:

~/.agent-memory/
├── default.json          # Default namespace
├── project_a.json        # Custom namespace
├── user_prefs.json       # Another namespace
└── _meta.json            # Global metadata

Configuration Options

Environment Variables

The server supports runtime configuration via environment variables:

VariableDefaultDescription
AGENT_MEMORY_PATH~/.agent-memory/Custom storage directory path

Server Configuration

The MCP server is configured with metadata:

# From server.py:238-242
server = Server(
    name="agent-memory",
    version="1.0.0",
    instructions="Agent Memory MCP — Persistent key-value memory for AI agents with TTL, namespaces, and search.",
    website_url="https://github.com/nousresearch/agent-memory-mcp",
)

Sources: server.py:238-242

MCP Client Integration

Registering the Server

Configure your MCP client to use the Agent Memory server:

#### For Claude Desktop

Add to your configuration file:

{
  "mcpServers": {
    "agent-memory": {
      "command": "python",
      "args": ["-m", "agent_memory_mcp"]
    }
  }
}

#### For Smithery.ai

The server is automatically available through the Smithery marketplace configuration:

# From smithery.yaml
capabilities:
  tools:
    - memory_remember
    - memory_recall
    - memory_forget
    - memory_search
    - memory_list_namespaces
    - memory_clear_namespace
    - memory_stats

Sources: smithery.yaml:8-15

Available Tools

Once installed, the following tools are available:

ToolDescription
memory_rememberStore a value with optional TTL
memory_recallRetrieve a value + metadata
memory_forgetDelete a key permanently
memory_searchSearch all namespaces by keyword
memory_list_namespacesList namespaces with counts
memory_clear_namespaceWipe a namespace
memory_statsGlobal storage statistics

Sources: index.html:1

Verification

Verify the installation by checking the server version:

python server.py --version

Or by calling the memory_stats tool:

{
  "name": "memory_stats",
  "arguments": {
    "format": "json"
  }
}

Expected response:

{
  "total_entries": 0,
  "total_size_bytes": 0,
  "namespace_count": 0,
  "storage_path": "/home/user/.agent-memory",
  "free_tier_limit": 1000,
  "pro_tier_limit": "unlimited"
}

Deployment Architecture

graph TD
    A[MCP Client] -->|stdio| B[Agent Memory MCP Server]
    B -->|fcntl lock| C[Storage Directory]
    C -->|JSON per NS| D[default.json]
    C -->|JSON per NS| E[custom.json]
    C -->|Metadata| F[_meta.json]
    
    G[Python Runtime] -->|mcp≥1.0.0| B

Licensing

Agent Memory MCP is distributed under the MIT License:

Sources: README.md:1

ItemValue
LicenseMIT
Version1.0.0
AuthorNous Research

Sources: smithery.yaml:17-21 Sources: glama.json:1-4

Troubleshooting

Windows without WSL

On Windows without WSL, file locking is disabled but the server remains functional. For production use on Windows, run via WSL.

Permission Errors

Ensure the user running the server has write access to ~/.agent-memory/:

mkdir -p ~/.agent-memory
chmod 755 ~/.agent-memory

MCP Connection Issues

Verify the MCP SDK is properly installed:

python -c "from mcp.server import Server; print('MCP SDK OK')"

Sources: server.py:34-41

Quick Start Guide

Related topics: Overview, Installation, memoryremember Tool, memoryrecall Tool

Section Related Pages

Continue reading this section for the full explanation and source context.

Section Prerequisites

Continue reading this section for the full explanation and source context.

Section Install via pip

Continue reading this section for the full explanation and source context.

Section Verify Installation

Continue reading this section for the full explanation and source context.

Related topics: Overview, Installation, memory_remember Tool, memory_recall Tool

Quick Start Guide

Overview

The Agent Memory MCP server provides a persistent key-value memory system for AI agents. It enables agents to retain context across sessions, solve context-window limitations, and maintain searchable long-term memory. This guide walks you through installation, configuration, and usage of all available tools.

System Architecture

graph TD
    A[AI Agent] -->|MCP Protocol| B[Agent Memory MCP Server]
    B --> C[~/.agent-memory/]
    C --> D[namespace1.json]
    C --> E[namespace2.json]
    C --> F[_meta.json]
    
    G[memory_remember] -->|Store| C
    H[memory_recall] -->|Retrieve| C
    I[memory_search] -->|Query| C
    J[memory_forget] -->|Delete| C

Installation

Prerequisites

RequirementVersion
Python3.10+
MCP SDK≥1.0.0

Install via pip

pip install agent-memory-mcp

Sources: requirements.txt:1

Verify Installation

After installation, the server runs as a stdio-based MCP server. No additional daemon setup is required.

Core Concepts

Namespaces

Namespaces provide isolated storage compartments for organizing memories by project, user, or domain. Each namespace stores entries as a separate JSON file in ~/.agent-memory/.

Default NamespacePurpose
defaultGeneral-purpose storage when no namespace is specified

Sources: server.py:44

Entry Structure

Each memory entry contains:

{
  "key": "unique_identifier",
  "value": "stored_content",
  "created_at": "2024-01-01T00:00:00.000Z",
  "accessed_at": "2024-01-01T00:00:00.000Z",
  "expires_at": null,
  "access_count": 0
}

TTL (Time-To-Live)

Entries can auto-expire after a specified duration. TTL is set in seconds with second-level precision using lazy expiry—the entry is removed on the next access after expiration.

Sources: server.py:200-215

Thread Safety

The server uses POSIX file locking (fcntl) to ensure safe concurrent access from multiple agents.

Sources: index.html

Available Tools

ToolPurposeDestructiveRead-Only
memory_rememberStore a valueNoNo
memory_recallRetrieve a valueNoYes
memory_forgetDelete a keyYesNo
memory_searchSearch by keywordNoYes
memory_list_namespacesList all namespacesNoYes
memory_clear_namespaceWipe a namespaceYesNo
memory_statsGet storage statisticsNoYes

Usage Examples

1. Store a Memory

Tool: memory_remember

Store a value under a key in a persistent namespace:

{
  "key": "user_preferences",
  "value": "dark_mode_enabled: true, language: en",
  "namespace": "settings",
  "ttl_seconds": 86400
}
ParameterTypeRequiredDescription
keystringYesUnique identifier for this memory entry
valuestringYesContent to store
namespacestringNoNamespace name (default: default)
ttl_secondsintegerNoAuto-expiry duration in seconds
formatstringNomarkdown or json (default: markdown)

Sources: server.py:130-175

Success Response:

## ✅ Success

**message:** Stored 'user_preferences' in namespace 'settings'
**key:** user_preferences
**namespace:** settings
**expires_in:** 86400s
**expires_at:** 2024-01-02T00:00:00.000Z

2. Retrieve a Memory

Tool: memory_recall

Fetch a stored value by key from a namespace:

{
  "key": "user_preferences",
  "namespace": "settings"
}
ParameterTypeRequiredDescription
keystringYesThe key to retrieve
namespacestringNoNamespace to look in (default: default)
formatstringNomarkdown or json (default: markdown)

Sources: server.py:177-220

Response includes:

  • Current value
  • Creation timestamp
  • Last access timestamp
  • Access count
  • Expiry status

3. Delete a Memory

Tool: memory_forget

Permanently delete a specific key:

{
  "key": "user_preferences",
  "namespace": "settings"
}
ParameterTypeRequiredDescription
keystringYesThe key to delete
namespacestringNoNamespace to delete from (default: default)
formatstringNomarkdown or json (default: markdown)

Sources: server.py:222-258

4. Search Memories

Tool: memory_search

Find memories across namespaces by keyword (case-insensitive substring match):

{
  "query": "preferences",
  "namespace": null,
  "limit": 10
}
ParameterTypeRequiredDescription
querystringYesSearch keyword or substring
namespacestringNoLimit search to specific namespace
limitintegerNoMaximum results (default: 10)
formatstringNomarkdown or json (default: markdown)

Search matches against both keys and values. Results are sorted by access count (highest first).

Sources: server.py:260-320

5. List All Namespaces

Tool: memory_list_namespaces

Display all namespaces with entry counts:

{
  "format": "markdown"
}

Response:

## ✅ Success

**namespace_count:** 3
**namespaces:** [3 items]
  - **namespace:** project_a
    - **active_entries:** 15
    - **expired_entries:** 2
  - **namespace:** project_b
    - **active_entries:** 8
    - **expired_entries:** 0
  - **namespace:** default
    - **active_entries:** 42
    - **expired_entries:** 5

Sources: server.py:322-360

6. Clear a Namespace

Tool: memory_clear_namespace

Delete ALL entries in a namespace permanently:

{
  "namespace": "project_a"
}
ParameterTypeRequiredDescription
namespacestringYesNamespace to clear

Warning: This action cannot be undone.

Sources: server.py:362-395

7. View Storage Statistics

Tool: memory_stats

Get comprehensive storage metrics:

{
  "format": "markdown"
}

Response includes:

MetricDescription
total_entriesTotal active memory entries
total_size_bytesStorage size in bytes
total_size_humanHuman-readable size
namespace_countNumber of namespaces
oldest_entryTimestamp of oldest entry
newest_entryTimestamp of newest entry
storage_pathDirectory path (~/.agent-memory/)
free_tier_limitFree tier entry limit (1000)

Sources: server.py:397-440

Storage Layout

~/.agent-memory/
├── _meta.json           # Global metadata
├── default.json         # Default namespace
├── project_a.json       # Custom namespace
├── project_b.json       # Custom namespace
└── ...

Each namespace file is a JSON array of entries:

[
  {
    "key": "example_key",
    "value": "example_value",
    "created_at": "2024-01-01T00:00:00.000Z",
    "accessed_at": "2024-01-01T00:00:00.000Z",
    "expires_at": null,
    "access_count": 5
  }
]

Sources: server.py:50-75

Response Formats

All tools support two response formats:

FormatUse Case
markdownHuman-readable output (default)
jsonProgrammatic parsing

Specify format via the format parameter in each tool call.

Workflow Diagram

graph LR
    A[Start] --> B{memory_remember?}
    B -->|Yes| C[Store Entry]
    B -->|No| D{memory_recall?}
    D -->|Yes| E[Retrieve + Update Access]
    D -->|No| F{memory_forget?}
    F -->|Yes| G[Delete Entry]
    F -->|No| H{memory_search?}
    H -->|Yes| I[Search All Namespaces]
    H -->|No| J{memory_list_namespaces?}
    J -->|Yes| K[List with Counts]
    J -->|No| L{memory_clear_namespace?}
    L -->|Yes| M[Delete All in Namespace]
    L -->|No| N{memory_stats?}
    N -->|Yes| O[Return Statistics]
    
    C --> P[Success]
    E --> P
    G --> P
    I --> P
    K --> P
    M --> P
    O --> P

Common Use Cases

Session Persistence

# Remember conversation context
memory_remember(
    key="session_123_context",
    value="User prefers technical explanations",
    namespace="sessions"
)

# Recall on next session
context = memory_recall(
    key="session_123_context",
    namespace="sessions"
)

Project-Scoped Memory

# Store project-specific data
memory_remember(
    key="api_endpoints",
    value="https://api.example.com/v1",
    namespace="project_alpha"
)

# Search across all projects
results = memory_search(query="api_endpoints")

Temporary Caching with TTL

# Cache with 1-hour expiry
memory_remember(
    key="rate_limit_status",
    value="{'requests': 450, 'limit': 500}",
    namespace="cache",
    ttl_seconds=3600
)

Error Handling

Error ConditionResponse
Empty keyKey must not be empty
Expired entryKey 'X' has expired
Key not foundKey 'X' not found in namespace 'Y'
Internal errorInternal error in {tool}: {exception}

All errors return a response with isError: true.

Sources: server.py:95-105

Configuration Options

OptionDefaultDescription
STORAGE_DIR~/.agent-memory/Storage directory location
DEFAULT_NAMESPACEdefaultFallback namespace
CHARACTER_LIMIT25,000Max response truncation

Sources: server.py:42-46

Next Steps

Sources: requirements.txt:1

Namespaces

Related topics: TTL Management

Section Related Pages

Continue reading this section for the full explanation and source context.

Section File Structure

Continue reading this section for the full explanation and source context.

Section Namespace Path Resolution

Continue reading this section for the full explanation and source context.

Section Memory Remember

Continue reading this section for the full explanation and source context.

Related topics: TTL Management

Namespaces

Overview

Namespaces in Agent Memory MCP provide isolated storage containers for memory entries, enabling logical separation of data across projects, users, domains, or any organizational scheme that fits an agent's workflow.

Each namespace functions as an independent key-value store backed by a separate JSON file in the filesystem, with POSIX file locking ensuring safe concurrent access.

Architecture

graph TD
    A[Agent Memory MCP] --> B[Namespace Router]
    B --> C[Namespace: default]
    B --> D[Namespace: project-a]
    B --> E[Namespace: user-123]
    B --> N[...more namespaces]
    
    C --> F["default.json<br/>(in ~/.agent-memory/)"]
    D --> G["project-a.json"]
    E --> H["user-123.json"]
    N --> I["namespace.json"]
    
    F --> J[_meta.json]
    G --> J
    H --> J
    I --> J
    
    style F fill:#e1f5fe
    style G fill:#e1f5fe
    style H fill:#e1f5fe
    style I fill:#e1f5fe
    style J fill:#fff9c4

Storage Model

File Structure

Namespaces are persisted as individual JSON files in the ~/.agent-memory/ directory:

ElementPathPurpose
Namespace files~/.agent-memory/{namespace}.jsonIndividual key-value stores
Metadata file~/.agent-memory/_meta.jsonGlobal statistics and entry counts

Sources: server.py:49-58

Namespace Path Resolution

def _namespace_path(namespace: str) -> Path:
    """Return the full path for a namespace JSON file."""
    # Sanitize the namespace so it can't escape the directory.
    safe = re.sub(r"[^a-zA-Z0-9_.\-]", "_", namespace)
    if not safe:
        safe = DEFAULT_NAMESPACE
    return STORAGE_DIR / f"{safe}.json"

The namespace is sanitized to prevent directory traversal attacks—only alphanumeric characters, underscores, dots, and hyphens are permitted. Invalid names default to "default".

Sources: server.py:63-72

Namespace Operations

Memory Remember

Stores a value under a key within a specified namespace.

def memory_remember(
    key: str,
    value: str,
    namespace: str = "default",
    ttl_seconds: Optional[int] = None,
    fmt: Optional[str] = None,
) -> str:
ParameterTypeDefaultDescription
keystring*required*Unique identifier for the memory entry
valuestring*required*Content to store
namespacestring"default"Target namespace
ttl_secondsintegernullTime-to-live in seconds (optional)
formatstring"markdown"Response format (markdown or json)

Sources: server.py:163-210

Memory Recall

Retrieves a value and its metadata from a namespace.

def memory_recall(
    key: str,
    namespace: str = "default",
    fmt: Optional[str] = None,
) -> str:
ParameterTypeDefaultDescription
keystring*required*Key to retrieve
namespacestring"default"Namespace to search
formatstring"markdown"Response format

Sources: server.py:212-261

Memory Forget

Permanently deletes a key from a namespace.

def memory_forget(
    key: str,
    namespace: str = "default",
    fmt: Optional[str] = None,
) -> str:
ParameterTypeDefaultDescription
keystring*required*Key to delete
namespacestring"default"Namespace to delete from
formatstring"markdown"Response format

Sources: server.py:263-303

Searches within a specific namespace or across all namespaces.

def memory_search(
    query: str,
    namespace: Optional[str] = None,
    limit: int = 10,
    fmt: Optional[str] = None,
) -> str:
ParameterTypeDefaultDescription
querystring*required*Search keyword (case-insensitive)
namespacestringnullLimit search to one namespace (searches all if omitted)
limitinteger10Maximum results to return
formatstring"markdown"Response format

Sources: server.py:305-365

Memory List Namespaces

Lists all namespaces with active and expired entry counts.

def memory_list_namespaces(fmt: Optional[str] = None) -> str:

Returns a table of all namespaces containing:

FieldDescription
namespaceNamespace name
active_entriesNon-expired entries
expired_entriesEntries past TTL

Sources: server.py:367-400

Memory Clear Namespace

Wipes all entries from a namespace permanently.

def memory_clear_namespace(
    namespace: str,
    fmt: Optional[str] = None,
) -> str:
ParameterTypeDefaultDescription
namespacestring*required*Namespace to clear
formatstring"markdown"Response format

Sources: server.py:402-427

Data Flow

sequenceDiagram
    participant Client
    participant Server
    participant FileSystem
    
    Client->>Server: memory_remember(key, value, namespace)
    Server->>Server: _namespace_path(namespace)
    Server->>Server: Sanitize namespace name
    Server->>Server: _read_namespace(namespace)
    Server->>Server: Append/update entry with metadata
    Server->>Server: _write_namespace(namespace, entries)
    Server->>Server: _recalc_meta()
    Server->>Client: Success response
    
    Client->>Server: memory_recall(key, namespace)
    Server->>Server: _read_namespace(namespace)
    Server->>Server: Check TTL expiration
    Server->>Server: Update access metadata
    Server->>Server: _write_namespace(namespace, entries)
    Server->>Client: Value + metadata

Thread Safety

Namespace operations use POSIX file locking via fcntl.flock to ensure safe concurrent access:

@contextmanager
def _locked_file(path: Path, mode: str = "r+"):
    """Open a file with an exclusive POSIX lock (fcntl.flock)."""
    _ensure_storage()
    file_exists = path.exists()
    if not file_exists and "w" in mode or "+" in mode:
        path.touch(exist_ok=True)
    fh = open(path, mode)
    try:
        try:
            fcntl.flock(fh, fcntl.LOCK_EX)
        except (NameError, OSError):
            pass  # platform without fcntl support
        yield fh
    finally:
        try:
            fcntl.flock(fh, fcntl.LOCK_UN)
        except (NameError, OSError):
            pass
        fh.close()

On platforms without fcntl support (e.g., Windows without WSL), the lock gracefully degrades to a no-op.

Sources: server.py:74-97

Metadata Tracking

Global metadata is recalculated by scanning all namespace files:

def _recalc_meta() -> None:
    """Recompute global metadata by scanning all namespaces."""
    total = 0
    namespace_count = 0
    for p in STORAGE_DIR.glob("*.json"):
        if p.stem == "_meta":
            continue
        namespace_count += 1
        entries = _read_namespace(p.stem)
        total += len([e for e in entries if not _is_expired(e)])
    _write_meta({"total_entries": total, "namespace_count": namespace_count})

Sources: server.py:446-457

Usage Examples

Storing Project-Specific Memories

# Store project configuration
memory_remember(
    key="config",
    value="debug_mode=true, max_connections=100",
    namespace="project-alpha"
)

# Store user preferences
memory_remember(
    key="theme",
    value="dark_mode",
    namespace="user-session-42"
)
# Search all namespaces for "config"
memory_search(query="config")

# Search only project-alpha namespace
memory_search(query="config", namespace="project-alpha")

Namespace Statistics

# Get overview of all namespaces
memory_list_namespaces()

Tool Annotations

Each namespace operation is annotated with MCP metadata indicating its behavior:

ToolreadOnlyHintdestructiveHintidempotentHint
memory_rememberfalsefalsetrue
memory_recalltruefalsetrue
memory_forgetfalsetruetrue
memory_searchtruefalsetrue
memory_list_namespacestruefalsetrue
memory_clear_namespacefalsetruetrue

Sources: server.py:113-430

Constants

ConstantValueDescription
DEFAULT_NAMESPACE"default"Fallback namespace when none specified
STORAGE_DIRPath.home() / ".agent-memory"Base directory for all data
CHARACTER_LIMIT25,000Maximum response size before truncation

Sources: server.py:44-49

Source: https://github.com/Rumblingb/agent-memory-mcp / Human Manual

TTL Management

Related topics: Namespaces, memoryremember Tool, memoryrecall Tool

Section Related Pages

Continue reading this section for the full explanation and source context.

Section Core Expiry Detection

Continue reading this section for the full explanation and source context.

Section TTL Storage on Entry Creation

Continue reading this section for the full explanation and source context.

Section Lazy Expiry Pattern

Continue reading this section for the full explanation and source context.

Related topics: Namespaces, memory_remember Tool, memory_recall Tool

TTL Management

Overview

TTL (Time-To-Live) Management provides automatic expiration for memory entries in the agent-memory-mcp system. This feature enables AI agents to store temporary information that self-destructs after a specified duration, making it ideal for caching, session data, and temporary context that becomes stale over time.

The TTL implementation uses a lazy expiry pattern where entries are checked and removed only when accessed, avoiding the overhead of background cleanup processes.

Architecture

graph TD
    A[memory_remember] --> B[Calculate expires_at]
    B --> C[Store entry with expires_at]
    C --> D[Write to JSON file]
    
    E[memory_recall] --> F[Find entry by key]
    F --> G{Is expired?}
    G -->|Yes| H[Delete entry]
    H --> I[Return Error]
    G -->|No| J[Update access metadata]
    J --> K[Return value]
    
    L[Background reads] --> M[_is_expired check]
    M --> N[Filter expired entries]
    N --> O[memory_list_namespaces]
    N --> P[memory_stats]

TTL Implementation Details

Core Expiry Detection

The _is_expired() function is the central mechanism for TTL validation:

def _is_expired(entry: Dict[str, Any]) -> bool:
    expires_at = entry.get("expires_at")
    if expires_at is None:
        return False
    return _now_unix() > expires_at
ComponentDescription
entry.get("expires_at")Retrieves the Unix timestamp when entry expires
None returnNo TTL set, entry never expires
True resultCurrent time exceeds expiry time

Sources: server.py

TTL Storage on Entry Creation

When memory_remember is called with a ttl_seconds parameter, the expiry timestamp is calculated:

entry = {
    "key": key,
    "value": value,
    "namespace": namespace,
    "created_at": _now_iso(),
    "accessed_at": _now_iso(),
    "expires_at": (now + ttl_seconds) if ttl_seconds else None,
    "access_count": 0,
}
ParameterTypeDescription
ttl_secondsintegerDuration in seconds before auto-expiry
expires_atfloat or NoneUnix timestamp for expiry, None for permanent

Sources: server.py

TTL Workflow

Lazy Expiry Pattern

sequenceDiagram
    participant Client
    participant Server
    participant Storage
    
    Client->>Server: memory_recall(key)
    Server->>Storage: Read namespace file
    Storage-->>Server: Entry found
    
    alt Entry is expired
        Server->>Storage: Delete expired entry
        Server-->>Client: Error: Key expired
    else Entry is valid
        Server->>Server: Update accessed_at
        Server->>Server: Increment access_count
        Server->>Storage: Write updated entry
        Server-->>Client: Return value + metadata
    end

The lazy expiry approach offers several advantages:

  1. No background processes - Expiry is handled during normal operations
  2. Immediate cleanup - Expired entries are removed on next access
  3. Minimal overhead - No periodic scanning of all entries

Expiry During Namespace Operations

Expired entries are filtered out when gathering statistics or listing namespaces:

active = [e for e in entries if not _is_expired(e)]
total_entries += len(active)

Sources: server.py

API Parameters

memory_remember

ParameterRequiredTypeDefaultDescription
keyYesstring-Unique identifier for the memory entry
valueYesstring-Content to store
namespaceNostring"default"Storage partition
ttl_secondsNointegerNoneSeconds until auto-expiry
formatNostring"markdown"Response format (markdown or json)

memory_recall

ParameterRequiredTypeDefaultDescription
keyYesstring-Key to retrieve
namespaceNostring"default"Namespace to search
formatNostring"markdown"Response format

Data Model

Memory Entry Schema

{
  "key": "session_token",
  "value": "abc123xyz",
  "namespace": "user_sessions",
  "created_at": "2024-01-15T10:30:00Z",
  "accessed_at": "2024-01-15T11:45:00Z",
  "expires_at": 1705322700,
  "access_count": 42
}
FieldTypeDescription
keystringUnique identifier within namespace
valuestringStored content
namespacestringLogical partition identifier
created_atISO8601 stringCreation timestamp
accessed_atISO8601 stringLast access timestamp
expires_atUnix timestamp or nullExpiry time (null = never expires)
access_countintegerNumber of times accessed

Sources: server.py

Usage Examples

Storing Temporary Data with TTL

Tool: memory_remember
Arguments: {
  "key": "oauth_token",
  "value": "eyJhbGciOiJIUzI1NiIs...",
  "namespace": "auth",
  "ttl_seconds": 3600
}

This stores an OAuth token that automatically expires after 1 hour.

Retrieving and Extending TTL

There is no built-in TTL extension mechanism. To extend an entry's lifetime:

  1. Use memory_recall to retrieve the current value
  2. Use memory_remember with a new ttl_seconds value
graph LR
    A[Recall entry] --> B[Get current value]
    B --> C[Forget old entry]
    C --> D[Remember with new TTL]

Performance Considerations

Expired Entry Handling

OperationExpired Entry Behavior
memory_rememberIgnored (new key/value)
memory_recallDeleted, returns error
memory_forgetNot found (already cleaned)
memory_searchExcluded from results
memory_list_namespacesCounted in expired_entries
memory_statsExcluded from totals

Storage File Structure

~/.agent-memory/
├── default.json      # Default namespace
├── auth.json         # Auth-related entries
├── projects.json     # Project-specific entries
└── _meta.json        # Global metadata

Each namespace is stored as a separate JSON file. Expired entries remain in the file until accessed, at which point they are removed during lazy expiry.

Sources: server.py

Limitations

  1. No automatic background cleanup - Expired entries persist in storage files until accessed
  2. No TTL modification - Existing entries cannot have their TTL updated; must recreate
  3. Second-level precision - TTL is calculated in seconds, not milliseconds
  4. No push notifications - No mechanism to alert when entries are about to expire

Sources: server.py

Data Storage

The Data Storage subsystem is the persistent layer of the Agent Memory MCP server. It handles all read/write operations to the filesystem, manages namespace isolation, enforces TTL (Time-T...

Section Directory Structure

Continue reading this section for the full explanation and source context.

Section Namespace Files

Continue reading this section for the full explanation and source context.

Section Sanitization Rules

Continue reading this section for the full explanation and source context.

Section Memory Entry Structure

Continue reading this section for the full explanation and source context.

The Data Storage subsystem is the persistent layer of the Agent Memory MCP server. It handles all read/write operations to the filesystem, manages namespace isolation, enforces TTL (Time-To-Live) expiry, and provides thread-safe concurrent access using POSIX file locking.

Overview

Agent Memory MCP uses a file-based JSON storage model where each namespace is stored as a separate JSON file. This design provides simplicity, portability, and easy inspection while maintaining isolation between different data domains.

PropertyValue
Storage Location~/.agent-memory/
File FormatJSON (one file per namespace)
Metadata File_meta.json
Concurrency ModelPOSIX file locking (fcntl)
Thread SafetyYes (via LOCK_EX exclusive locks)

Sources: server.py:36-38

Storage Architecture

Directory Structure

~/.agent-memory/
├── default.json          # Default namespace
├── project_a.json        # Custom namespace
├── project_b.json        # Custom namespace
└── _meta.json             # Global statistics metadata

Namespace Files

Each namespace is stored as a standalone JSON array containing memory entries. The filename is derived from the namespace name with character sanitization applied.

def _namespace_path(namespace: str) -> Path:
    """Return the full path for a namespace JSON file."""
    # Sanitize the namespace so it can't escape the directory.
    safe = re.sub(r"[^a-zA-Z0-9_.\-]", "_", namespace)
    if not safe:
        safe = DEFAULT_NAMESPACE
    return STORAGE_DIR / f"{safe}.json"

Sources: server.py:54-61

Sanitization Rules

Character ClassActionResult
[a-zA-Z0-9_.\-]AllowedPreserved
All othersReplaced_ (underscore)
Empty after sanitizationFallbackdefault namespace

This prevents directory traversal attacks and ensures safe filesystem operations.

Data Models

Memory Entry Structure

{
  "key": "user_preference_theme",
  "value": "dark_mode",
  "created_at": "2024-01-15T10:30:00.000Z",
  "accessed_at": "2024-01-15T14:22:00.000Z",
  "expires_at": "2024-02-15T10:30:00.000Z",
  "access_count": 42
}
FieldTypeDescription
keystringUnique identifier within namespace
valuestringStored content (any text)
created_atISO 8601 stringCreation timestamp
accessed_atISO 8601 stringLast access timestamp
expires_atISO 8601 string or nullTTL expiry timestamp (null = never)
access_countintegerNumber of times accessed

Sources: server.py:189-195

Metadata File Structure (`_meta.json`)

{
  "total_entries": 150,
  "namespace_count": 5
}
FieldTypeDescription
total_entriesintegerTotal active (non-expired) entries
namespace_countintegerNumber of namespace files

Storage Operations

Read Flow

graph TD
    A[memory_recall] --> B[Validate key not empty]
    B --> C[_read_namespace namespace]
    C --> D[Find entry by key]
    D --> E{Entry exists?}
    E -->|Yes| F{Is expired?}
    E -->|No| G[Return error: key not found]
    F -->|Yes| H[Remove expired entry]
    F -->|No| I[Update access metadata]
    H --> G
    I --> J[Increment access_count]
    J --> K[Update accessed_at]
    K --> L[Return value + metadata]

Write Flow

graph TD
    A[memory_remember] --> B[Validate key and value]
    B --> C[_read_namespace namespace]
    C --> D[Check for existing key]
    D --> E{Key exists?}
    E -->|Yes| F[Update existing entry]
    E -->|No| G[Create new entry]
    F --> H[_write_namespace]
    G --> H
    H --> I[_recalc_meta]
    I --> J[Return success]

Core Storage Functions

File Locking

Thread-safety is achieved through POSIX file locking using fcntl:

@contextmanager
def _locked_file(path: Path, mode: str):
    """Open a file with exclusive locking (POSIX fcntl)."""
    _ensure_storage()
    fh = open(path, mode)
    try:
        fcntl.flock(fh.fileno(), fcntl.LOCK_EX)
        yield fh
    finally:
        try:
            os.fsync(fh.fileno())
        except OSError:
            pass
        fh.close()
OperationLock TypeReason
ReadLOCK_EX (exclusive)Ensures consistent read after write
WriteLOCK_EX (exclusive)Prevents concurrent modifications
os.fsyncAfter writeGuarantees durability

Sources: server.py:88-102

Read Namespace

def _read_namespace(namespace: str) -> List[Dict[str, Any]]:
    """Read all entries for a namespace (returns list, never None)."""
    path = _namespace_path(namespace)
    if not path.exists():
        return []
    with _locked_file(path, "r") as fh:
        try:
            fh.seek(0)
            raw = fh.read()
            if not raw.strip():
                return []
            return json.loads(raw)
        except (json.JSONDecodeError, OSError):
            return []

Key behaviors:

  • Returns empty list for non-existent namespaces
  • Handles malformed JSON gracefully (returns empty list)
  • Handles empty files gracefully

Write Namespace

def _write_namespace(namespace: str, entries: List[Dict[str, Any]]) -> None:
    """Atomically write all entries for a namespace."""
    _ensure_storage()
    path = _namespace_path(namespace)
    with _locked_file(path, "w") as fh:
        fh.seek(0)
        fh.truncate()
        json.dump(entries, fh, indent=2)
        fh.flush()

Atomicity guarantees:

  1. Truncate file first (removes old data)
  2. Write complete JSON array
  3. Flush to kernel buffer
  4. Release lock (triggers fsync)

TTL (Time-To-Live) Management

Expiry Detection

def _is_expired(entry: Dict[str, Any]) -> bool:
    """Check if an entry has expired based on its TTL."""
    if entry.get("expires_at") is None:
        return False
    return _now_unix() > entry["expires_at"]

Lazy Expiry Strategy

TTL enforcement uses a lazy deletion approach:

EventAction
memory_recallCheck expiry, delete if expired
memory_searchSkip expired entries (no deletion)
memory_forgetNo expiry check needed
Background cleanupNone (relies on lazy deletion)

This design:

  • Avoids expensive background processes
  • Ensures expired entries are never returned
  • Accepts slight disk usage increase from expired entries until next access

TTL Entry Lifecycle

graph LR
    A[Created] --> B[Active]
    B -->|TTL reached| C[Expired]
    C -->|Next recall| D[Deleted]
    C -->|Next search| E[Skipped]

Metadata Management

Global Metadata Recalculation

def _recalc_meta() -> None:
    """Recompute global metadata by scanning all namespaces."""
    total = 0
    namespace_count = 0
    for p in STORAGE_DIR.glob("*.json"):
        if p.stem == "_meta":
            continue
        namespace_count += 1
        entries = _read_namespace(p.stem)
        total += len([e for e in entries if not _is_expired(e)])
    _write_meta({"total_entries": total, "namespace_count": namespace_count})

recalc_meta() is called after:

  • memory_remember (new entry added)
  • memory_forget (entry deleted)
  • memory_clear_namespace (namespace wiped)
  • memory_recall (lazy expiry cleanup)

Sources: server.py:152-164

Storage Statistics

The memory_stats function aggregates information from all namespace files:

def memory_stats(fmt: Optional[str] = None) -> str:
    """Get storage statistics."""
    total_entries = 0
    total_size = 0
    namespace_count = 0
    oldest: Optional[str] = None
    newest: Optional[str] = None

    for p in STORAGE_DIR.glob("*.json"):
        if p.stem == "_meta":
            continue
        namespace_count += 1
        try:
            file_size = p.stat().st_size
            total_size += file_size
        except OSError:
            pass
        entries = _read_namespace(p.stem)
        active = [e for e in entries if not _is_expired(e)]
        total_entries += len(active)
        for e in active:
            created = e.get("created_at")
            if created:
                if oldest is None or created < oldest:
                    oldest = created
                if newest is None or created > newest:
                    newest = created

Stats returned:

StatDescription
total_entriesSum of active entries across all namespaces
total_size_bytesDisk usage in bytes
total_size_humanHuman-readable size (B, KB, MB, GB, TB)
namespace_countNumber of namespace files
oldest_entryISO timestamp of earliest entry
newest_entryISO timestamp of most recent entry
storage_pathFilesystem path to storage directory
free_tier_limit1000 entries
pro_tier_limitunlimited

Sources: server.py:271-307

Access Tracking

Every memory entry maintains access metadata:

FieldPurposeUpdated When
created_atCreation timestampEntry creation
accessed_atLast access timestampmemory_recall
access_countTotal access countmemory_recall
# In memory_recall
entry["accessed_at"] = _now_iso()
entry["access_count"] = entry.get("access_count", 0) + 1
_write_namespace(namespace, entries)

Access count is used by memory_search to rank results by relevance.

Namespace Isolation

Namespaces provide logical separation of memory entries:

FeatureBehavior
Separate filesEach namespace has its own .json file
Independent entriesKeys only unique within namespace
Independent TTLsEach entry has its own expiry
Independent accessSeparate metadata per entry

Search behavior by namespace:

ParameterSearch Scope
namespace omittedAll namespaces
namespace specifiedSingle namespace only
if namespace:
    namespaces_to_search = [namespace]
else:
    namespaces_to_search = [
        p.stem
        for p in STORAGE_DIR.glob("*.json")
        if p.stem != "_meta"
    ]

Error Handling

Error ConditionHandling
Malformed JSON fileReturn empty list, log error
Empty fileReturn empty list
Missing namespaceCreate empty file on write
Lock acquisition failureBlock until lock available
Disk fullOS-level error propagated

Performance Characteristics

AspectValue/Note
File I/OBlocking (synchronous)
Lock scopePer-operation (not transaction)
Search complexityO(n) across namespaces
Memory per namespaceProportional to entries
Disk I/O per access1 read + 1 write

Configuration Constants

CHARACTER_LIMIT = 25_000      # Maximum output truncation
DEFAULT_NAMESPACE = "default" # Fallback namespace
STORAGE_DIR = Path.home() / ".agent-memory"
META_FILE = "_meta.json"

Sources: server.py:30-35

Sources: server.py:36-38

memory_remember Tool

Related topics: memoryrecall Tool, TTL Management, Quick Start Guide

Section Related Pages

Continue reading this section for the full explanation and source context.

Section Parameters

Continue reading this section for the full explanation and source context.

Section Step-by-Step Process

Continue reading this section for the full explanation and source context.

Section Success Response (Markdown)

Continue reading this section for the full explanation and source context.

Related topics: memory_recall Tool, TTL Management, Quick Start Guide

memory_remember Tool

The memory_remember tool is the primary data ingestion function in the Agent Memory MCP server, enabling AI agents to persistently store key-value pairs with optional time-to-live (TTL) expiration. It serves as the foundation for creating durable memory entries that survive session restarts, context window limits, and agent crashes.

Overview

memory_remember provides a mechanism for AI agents to store arbitrary string values under unique keys within isolated namespace containers. Each entry captures rich metadata including creation timestamps, access tracking, and optional expiration timers.

Core Responsibilities:

  • Accept key-value pairs for persistent storage
  • Support optional TTL-based automatic expiration
  • Organize entries within user-defined namespaces
  • Track access statistics (count, timestamps)
  • Enforce atomic writes with POSIX file locking
  • Update global metadata counters

Sources: server.py:1-50

Function Signature

def memory_remember(
    key: str,
    value: str,
    namespace: str = DEFAULT_NAMESPACE,
    ttl_seconds: Optional[int] = None,
    fmt: Optional[str] = None,
) -> str

Parameters

ParameterTypeRequiredDefaultDescription
keystringYesUnique identifier for the memory entry
valuestringYesThe content/value to store persistently
namespacestringNo"default"Logical container for organizing entries
ttl_secondsintegerNonullTime-to-live in seconds; entry auto-expires after this duration
formatstringNo"markdown"Response format: "markdown" or "json"

Sources: server.py:180-230

Data Model

Each stored entry maintains the following schema:

{
    "key": str,           # User-provided unique identifier
    "value": str,         # Stored content
    "namespace": str,     # Container identifier
    "created_at": str,    # ISO 8601 timestamp (UTC)
    "accessed_at": str,   # ISO 8601 timestamp, updated on each retrieval
    "expires_at": Optional[str],  # ISO 8601 timestamp or None
    "access_count": int   # Cumulative retrieval count
}

Sources: server.py:200-220

Storage Architecture

Entries are persisted to the filesystem using a namespace-per-file approach:

~/.agent-memory/
├── default.json       # Default namespace
├── project_a.json     # Custom namespace
├── project_b.json     # Custom namespace
└── _meta.json         # Global statistics

Storage Details:

AspectSpecification
Storage Location~/.agent-memory/
File FormatOne JSON file per namespace
Namespace File NamingSanitized: [^a-zA-Z0-9_.\-]_
Locking MechanismPOSIX fcntl.flock()
Atomic WritesTruncate-then-write pattern

Sources: server.py:60-80

Execution Flow

graph TD
    A[memory_remember Called] --> B{Validate Inputs}
    B -->|Empty Key| C[Return Error]
    B -->|Valid Key| D[Acquire File Lock]
    D --> E[Read Namespace File]
    E --> F[Check for Duplicate Key]
    F -->|Key Exists| G[Update Existing Entry]
    F -->|New Key| H[Append New Entry]
    G --> I[Write Namespace File]
    H --> I
    I --> J[Release File Lock]
    J --> K[Recalculate Global Metadata]
    K --> L[Return Success Response]
    C --> L2[Return Error Response]

Step-by-Step Process

  1. Input Validation: Reject empty or whitespace-only keys
  2. Lock Acquisition: Obtain exclusive POSIX file lock on namespace file
  3. Entry Creation: Build entry dict with timestamps and TTL calculation
  4. File Write: Atomically write updated entries array
  5. Metadata Update: Recalculate global entry counts via _recalc_meta()
  6. Response Generation: Format success data as markdown or JSON

Sources: server.py:195-230

TTL (Time-to-Live) Mechanism

The TTL feature enables automatic expiration of entries after a specified duration:

entry = {
    "key": key,
    "value": value,
    "namespace": namespace,
    "created_at": _now_iso(),
    "accessed_at": _now_iso(),
    "expires_at": (now + ttl_seconds) if ttl_seconds else None,
    "access_count": 0,
}

TTL Behavior:

  • TTL is calculated as current_time + ttl_seconds at write time
  • Expired entries are lazily deleted during memory_recall operations
  • TTL precision is second-level (not millisecond)
  • A value of null or omitting ttl_seconds creates a never-expiring entry

Sources: server.py:205-215

Response Format

Success Response (Markdown)

## ✅ Success

**message:** Stored 'user_preference' in namespace 'default'
**key:** user_preference
**namespace:** default
**expires_in:** 3600s
**expires_at:** 2024-01-15T10:30:00Z

Success Response (JSON)

{
  "status": "ok",
  "message": "Stored 'user_preference' in namespace 'default'",
  "key": "user_preference",
  "namespace": "default",
  "expires_in": "3600s",
  "expires_at": "2024-01-15T10:30:00Z"
}

Error Response

{
  "status": "error",
  "error": "Key must not be empty",
  "isError": true
}

Sources: server.py:300-350

Namespace Handling

Namespaces provide logical isolation for memory entries:

graph LR
    subgraph Storage Layer
        NA[Namespace A<br/>namespace_a.json]
        NB[Namespace B<br/>namespace_b.json]
        NC[Default<br/>default.json]
    end
    
    A1[Entry 1] --> NA
    A2[Entry 2] --> NA
    B1[Entry 3] --> NB
    C1[Entry 4] --> NC

Namespace Rules:

  • Default namespace is "default" when unspecified
  • Names are sanitized to prevent directory traversal attacks
  • Each namespace persists independently
  • Cross-namespace search available via memory_search

Sources: server.py:65-75

Thread Safety

The implementation ensures safe concurrent access through POSIX file locking:

@contextmanager
def _locked_file(path: Path, mode: str):
    fh = open(path, mode)
    fcntl.flock(fh.fileno(), fcntl.LOCK_EX)
    try:
        yield fh
    finally:
        fcntl.flock(fh.fileno(), fcntl.LOCK_UN)
        fh.close()
  • Lock Type: Exclusive (LOCK_EX) for both reads and writes
  • Scope: Per-file locks prevent corruption during concurrent access
  • Guarantee: Prevents race conditions from multiple agents

Sources: server.py:85-100

MCP Tool Definition

The tool is registered with the MCP server as follows:

Tool(
    name="memory_remember",
    description="Store a value under a key in a persistent memory namespace. Optionally set a TTL (time-to-live) in seconds for automatic expiry.",
    inputSchema={
        "type": "object",
        "properties": {
            "key": {"type": "string", "description": "Unique key for this memory entry."},
            "value": {"type": "string", "description": "The value/content to store."},
            "namespace": {
                "type": "string",
                "description": "Namespace to store the entry in (default: 'default').",
                "default": "default",
            },
            "ttl_seconds": {
                "type": "integer",
                "description": "Optional TTL in seconds. Entry auto-expires after this duration.",
            },
            "format": {
                "type": "string",
                "enum": ["markdown", "json"],
                "description": "Response format (default: markdown).",
                "default": "markdown",
            },
        },
        "required": ["key", "value"],
    },
    annotations=ToolAnnotations(
        readOnlyHint=False,
        destructiveHint=False,
        idempotentHint=True,
        openWorldHint=False,
    ),
)

Tool Annotations:

AnnotationValueMeaning
readOnlyHintfalseModifies server state
destructiveHintfalseDoes not delete existing data
idempotentHinttrueSafe to retry
openWorldHintfalseOperates on local storage only

Sources: server.py:280-320

Usage Examples

Basic Storage

Tool: memory_remember
Arguments: {
    "key": "user_name",
    "value": "Alice",
    "namespace": "users"
}

Storage with TTL (1 hour)

Tool: memory_remember
Arguments: {
    "key": "session_token",
    "value": "abc123xyz",
    "namespace": "sessions",
    "ttl_seconds": 3600
}

JSON Response Format

Tool: memory_remember
Arguments: {
    "key": "config",
    "value": "{\"theme\": \"dark\", \"lang\": \"en\"}",
    "namespace": "settings",
    "format": "json"
}
ToolPurposeRelationship
memory_recallRetrieve stored values by keyComplements write with read
memory_forgetDelete a specific entryInverse operation
memory_searchFind entries by keywordDiscovery of stored data
memory_list_namespacesList all namespacesNamespace enumeration
memory_statsView storage statisticsGlobal monitoring

Sources: server.py:320-380

Limitations

  • Character Limit: Stored values are truncated at 25,000 characters via _truncate() helper
  • Key Constraints: Empty keys are rejected; keys are case-sensitive
  • Storage Backend: Filesystem-based (not suitable for high-frequency write workloads)
  • Free Tier: Limited to 1,000 total entries across all namespaces
  • No Batch Operations: Single key-value pair per call only

Sources: server.py:55 and smithery.yaml

Sources: server.py:1-50

memory_recall Tool

Related topics: memoryremember Tool, memoryforget Tool, TTL Management

Section Related Pages

Continue reading this section for the full explanation and source context.

Section Parameters

Continue reading this section for the full explanation and source context.

Section Success Response (Markdown)

Continue reading this section for the full explanation and source context.

Section Success Response (JSON)

Continue reading this section for the full explanation and source context.

Related topics: memory_remember Tool, memory_forget Tool, TTL Management

memory_recall Tool

Overview

The memory_recall tool is a core retrieval function in the Agent Memory MCP server that enables AI agents to fetch previously stored values from persistent memory. It provides lazy TTL (Time-To-Live) expiry checking, automatic access tracking, and metadata enrichment on every retrieval operation.

Key Characteristics:

AttributeValue
Tool Namememory_recall
CategoryRead operation
DestructiveNo
IdempotentYes
Default Namespacedefault
Response Formatsmarkdown, json

Sources: server.py

Function Signature

def memory_recall(
    key: str,
    namespace: str = DEFAULT_NAMESPACE,
    fmt: Optional[str] = None,
) -> str:

Parameters

ParameterTypeRequiredDefaultDescription
keystringYesThe unique key identifying the memory entry to retrieve
namespacestringNo"default"The namespace to search within
fmtstringNo"markdown"Response format: "markdown" or "json"

Sources: server.py

Tool Schema Definition

{
  "name": "memory_recall",
  "description": "Retrieve a stored value by key from a namespace. Returns full metadata including creation time, last access, and expiry. Automatically expires TTL'd entries.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "key": {
        "type": "string",
        "description": "The key to retrieve."
      },
      "namespace": {
        "type": "string",
        "description": "Namespace to look in (default: 'default').",
        "default": "default"
      },
      "format": {
        "type": "string",
        "enum": ["markdown", "json"],
        "description": "Response format (default: markdown).",
        "default": "markdown"
      }
    },
    "required": ["key"]
  },
  "annotations": {
    "readOnlyHint": false,
    "destructiveHint": false,
    "idempotentHint": true,
    "openWorldHint": false
  }
}

Sources: server.py

Workflow

graph TD
    A[Start: memory_recall called] --> B{Key provided?}
    B -->|No| C[Return Error: Key must not be empty]
    B -->|Yes| D[Read namespace file]
    E[Find entry by key] --> F{Entry found?}
    F -->|No| G[Return Error: Key not found]
    F -->|Yes| H{Entry expired?}
    D --> E
    H -->|Yes| I[Remove expired entry from file]
    I --> J[Recalculate metadata]
    J --> K[Return Error: Key has expired]
    H -->|No| L[Update access metadata]
    L --> M[Increment access_count]
    M --> N[Update accessed_at timestamp]
    N --> O[Write updated namespace file]
    O --> P[Return Success with value and metadata]

Lazy TTL Expiry Mechanism

One of the key features of memory_recall is its lazy expiry behavior. Rather than running a background cleanup task, expired entries are detected and removed when they are accessed:

for i, entry in enumerate(entries):
    if entry["key"] == key:
        if _is_expired(entry):
            # Lazy expiry – remove and return not-found
            entries.pop(i)
            _write_namespace(namespace, entries)
            _recalc_meta()
            return _error(f"Key '{key}' has expired", fmt)

Expiry Detection Logic:

FieldPurpose
expires_atISO timestamp when entry expires
_is_expired(entry)Returns True if current time > expires_at

When an entry expires:

  1. The entry is removed from the namespace file immediately
  2. Global metadata is recalculated
  3. An error response is returned indicating expiration

Sources: server.py

Access Tracking

Every successful recall operation automatically updates metadata:

# Update access metadata
entry["accessed_at"] = _now_iso()
entry["access_count"] = entry.get("access_count", 0) + 1
_write_namespace(namespace, entries)

Tracked Metadata:

FieldTypeDescription
accessed_atISO stringTimestamp of most recent access
access_countintegerTotal number of times this entry has been accessed

This data powers the search ranking algorithm, which sorts results by access_count in descending order.

Sources: server.py

Response Format

Success Response (Markdown)

## ✅ Success

**key:** example_key
**namespace:** default
**value:** The stored value content
**created_at:** 2024-01-15T10:30:00
**accessed_at:** 2024-01-15T14:22:00
**access_count:** 5
**expires_at:** 2024-01-16T10:30:00 or Never

Success Response (JSON)

{
  "status": "ok",
  "key": "example_key",
  "namespace": "default",
  "value": "The stored value content",
  "created_at": "2024-01-15T10:30:00",
  "accessed_at": "2024-01-15T14:22:00",
  "access_count": 5,
  "expires_at": "2024-01-16T10:30:00"
}

Error Responses

Error ConditionMessage
Empty keyKey must not be empty
Key not foundKey 'example_key' not found in namespace 'default'
Entry expiredKey 'example_key' has expired

Sources: server.py

Data Storage Model

Entries are stored in JSON files within the storage directory:

~/.agent-memory/
├── _meta.json
├── default.json
├── project_a.json
└── user_b.json

Entry Schema

{
  "key": "string",
  "value": "string",
  "created_at": "ISO8601 timestamp",
  "accessed_at": "ISO8601 timestamp",
  "expires_at": "ISO8601 timestamp or null",
  "access_count": 0
}

Thread Safety

File operations use POSIX file locking (fcntl) to ensure safe concurrent access:

@contextmanager
def _locked_file(path: Path, mode: str):
    fh = open(path, mode)
    try:
        fcntl.flock(fh.fileno(), fcntl.LOCK_EX)
        yield fh
    finally:
        fcntl.flock(fh.fileno(), fcntl.LOCK_UN)
        fh.close()

This ensures that multiple agents can safely read and write to the memory store simultaneously without data corruption.

Sources: server.py

Usage Examples

Basic Recall

Tool: memory_recall
Arguments: {"key": "user_preference", "namespace": "default"}

Recalling from Specific Namespace

Tool: memory_recall
Arguments: {"key": "session_state", "namespace": "user_123"}

JSON Response Format

Tool: memory_recall
Arguments: {"key": "config", "namespace": "settings", "format": "json"}

Integration with Other Tools

Related ToolInteraction
memory_rememberStores data that memory_recall retrieves
memory_searchUses access_count to rank search results
memory_forgetPermanently deletes entries
memory_statsTracks global total_entries count

The search functionality prioritizes frequently accessed entries:

# Sort by access count desc, then created_at desc
results.sort(key=lambda x: (-x.get("access_count", 0), x.get("created_at", "")))

Sources: server.py

Error Handling

ScenarioHandling
Empty keyReturns error immediately before file I/O
Non-existent namespaceReturns empty array; treated as "key not found"
Corrupted JSON fileReturns empty array, log error
Expired entryRemoves from file, returns expiration error
File permission errorsCaught by try/except, returns error

The tool handles exceptions gracefully at the server level:

except Exception as exc:
    err_text = _error(f"Internal error in {name}: {exc}", fmt)
    return CallToolResult(
        content=[TextContent(type="text", text=err_text)],
        isError=True,
    )

Sources: server.py

Sources: server.py

memory_forget Tool

Related topics: memoryrecall Tool

Section Related Pages

Continue reading this section for the full explanation and source context.

Section Step-by-Step Breakdown

Continue reading this section for the full explanation and source context.

Section readnamespace()

Continue reading this section for the full explanation and source context.

Section writenamespace()

Continue reading this section for the full explanation and source context.

Related topics: memory_recall Tool

memory_forget Tool

The memory_forget tool is a destructive operation within the Agent Memory MCP server that permanently removes a specific key-value entry from a designated memory namespace. It is one of seven tools exposed by the MCP server and represents the deletion capability in the persistent key-value storage system.

Overview

Agent Memory MCP provides AI agents with persistent memory across sessions. The system stores data as JSON files in ~/.agent-memory/, with one file per namespace plus a _meta.json statistics file. The memory_forget tool enables agents to selectively remove entries when they are no longer needed, helping manage storage and maintain relevant data.

Tool NamePurposeDestructiveRead-Only
memory_rememberStore a key-value pairNoNo
memory_recallRetrieve a value by keyNoYes
memory_forgetDelete a key permanentlyYesNo
memory_searchSearch across namespacesNoYes
memory_list_namespacesList all namespacesNoYes
memory_clear_namespaceWipe entire namespaceYesNo
memory_statsGet storage statisticsNoYes

Function Signature

The memory_forget function is defined in server.py and accepts the following parameters:

def memory_forget(
    key: str,
    namespace: str = DEFAULT_NAMESPACE,
    fmt: Optional[str] = None,
) -> str:
ParameterTypeRequiredDefaultDescription
keystringYesUnique key identifying the memory entry to delete
namespacestringNo"default"Namespace containing the entry
formatstringNo"markdown"Response format: "markdown" or "json"

Tool Definition

The MCP server exposes memory_forget with the following schema definition:

Tool(
    name="memory_forget",
    description="Permanently delete a key from a memory namespace.",
    inputSchema={
        "type": "object",
        "properties": {
            "key": {
                "type": "string",
                "description": "Unique key for this memory entry.",
            },
            "namespace": {
                "type": "string",
                "description": "Namespace to delete from (default: 'default').",
                "default": "default",
            },
            "format": {
                "type": "string",
                "enum": ["markdown", "json"],
                "description": "Response format (default: markdown).",
                "default": "markdown",
            },
        },
        "required": ["key"],
    },
    annotations=ToolAnnotations(
        readOnlyHint=False,
        destructiveHint=True,
        idempotentHint=True,
        openWorldHint=False,
    ),
),

Tool Annotations

The tool is annotated with specific behavioral hints that inform MCP clients about its characteristics:

AnnotationValueImplication
readOnlyHintfalseThis tool modifies state
destructiveHinttrueThis operation permanently removes data
idempotentHinttrueMultiple calls with same key produce same result (success or not-found)
openWorldHintfalseOnly affects local storage, no external world interaction

Execution Flow

The following diagram illustrates the execution flow when memory_forget is invoked:

graph TD
    A[Call memory_forget with key and namespace] --> B{Key is empty?}
    B -->|Yes| C[Return error: Key must not be empty]
    B -->|No| D[Read namespace file from ~/.agent-memory/]
    D --> E{Namespace file exists?}
    E -->|No| F[Return error: Key not found]
    E -->|Yes| G[Parse JSON entries list]
    G --> H{Entry with matching key exists?}
    H -->|No| I[Return error: Key not found]
    H -->|Yes| J[Remove entry from list]
    J --> K[Write updated entries back to namespace file]
    K --> L[Recalculate global metadata]
    L --> M[Return success response]

Implementation Details

The core implementation of memory_forget performs the following operations:

def memory_forget(
    key: str,
    namespace: str = DEFAULT_NAMESPACE,
    fmt: Optional[str] = None,
) -> str:
    """Delete a key permanently from a namespace."""
    if not key.strip():
        return _error("Key must not be empty", fmt)

    entries = _read_namespace(namespace)

    for i, entry in enumerate(entries):
        if entry["key"] == key:
            entries.pop(i)
            _write_namespace(namespace, entries)
            _recalc_meta()

            return _success(
                {
                    "message": f"Deleted '{key}' from namespace '{namespace}'",
                    "key": key,
                    "namespace": namespace,
                },
                fmt,
            )

    return _error(f"Key '{key}' not found in namespace '{namespace}'", fmt)

Step-by-Step Breakdown

  1. Input Validation: Checks that the key is not empty or whitespace-only. Empty keys return an error immediately.
  1. Read Namespace: Loads all entries from the namespace's JSON file using the thread-safe _read_namespace() helper.
  1. Search for Entry: Iterates through entries to find a matching key.
  1. Remove Entry: If found, removes the entry from the list using pop(i).
  1. Write Back: Persists the modified entries list to the namespace file using _write_namespace().
  1. Update Metadata: Calls _recalc_meta() to update global statistics reflecting the entry count change.
  1. Return Response: Returns a formatted success or error message.

File Operations

The tool relies on two critical helper functions for file I/O:

_read_namespace()

def _read_namespace(namespace: str) -> List[Dict[str, Any]]:
    """Read all entries for a namespace (returns list, never None)."""
    path = _namespace_path(namespace)
    if not path.exists():
        return []
    with _locked_file(path, "r") as fh:
        try:
            fh.seek(0)
            raw = fh.read()
            if not raw.strip():
                return []
            return json.loads(raw)
        except (json.JSONDecodeError, OSError):
            return []

_write_namespace()

def _write_namespace(namespace: str, entries: List[Dict[str, Any]]) -> None:
    """Atomically write all entries for a namespace."""
    _ensure_storage()
    path = _namespace_path(namespace)
    with _locked_file(path, "w") as fh:
        fh.seek(0)
        fh.truncate()
        json.dump(entries, fh, indent=2)
        fh.flush()

Both operations use POSIX file locking via _locked_file() context manager to ensure thread-safe concurrent access.

Namespace Path Resolution

Namespace names are sanitized to prevent directory escape attacks:

def _namespace_path(namespace: str) -> Path:
    """Return the full path for a namespace JSON file."""
    safe = re.sub(r"[^a-zA-Z0-9_.\-]", "_", namespace)
    if not safe:
        safe = DEFAULT_NAMESPACE
    return STORAGE_DIR / f"{safe}.json"

This ensures all namespace files remain within ~/.agent-memory/ directory. Sources: server.py

Metadata Recalculation

After successful deletion, global metadata is updated:

def _recalc_meta() -> None:
    """Recompute global metadata by scanning all namespaces."""
    total = 0
    namespace_count = 0
    for p in STORAGE_DIR.glob("*.json"):
        if p.stem == "_meta":
            continue
        namespace_count += 1
        entries = _read_namespace(p.stem)
        total += len([e for e in entries if not _is_expired(e)])
    _write_meta({"total_entries": total, "namespace_count": namespace_count})

The function scans all namespace files to count active (non-expired) entries and updates the _meta.json file accordingly.

Response Formats

Success Response (Markdown)

## ✅ Success
**message:** Deleted 'user_preference' from namespace 'default'
**key:** user_preference
**namespace:** default

Success Response (JSON)

{
  "status": "ok",
  "message": "Deleted 'user_preference' from namespace 'default'",
  "key": "user_preference",
  "namespace": "default"
}

Error Response (Key Not Found)

## ❌ Error
**Key 'nonexistent_key' not found in namespace 'default'**

Error Handling

ScenarioError MessageHTTP-like Status
Empty key"Key must not be empty"Validation Error
Key not in namespace"Key '{key}' not found in namespace '{namespace}'"Not Found

Errors are formatted consistently via _error() helper and returned with isError=True in JSON mode.

Routing in MCP Server

The MCP server routes tool calls through the call_tool handler:

@server.call_tool()
async def call_tool(name: str, arguments: Dict[str, Any]) -> CallToolResult:
    """Route tool calls to the appropriate implementation."""
    fmt = arguments.pop("format", "markdown")

    try:
        if name == "memory_remember":
            text = memory_remember(**arguments, fmt=fmt)
        elif name == "memory_recall":
            text = memory_recall(**arguments, fmt=fmt)
        elif name == "memory_forget":
            text = memory_forget(**arguments, fmt=fmt)
        # ... other tools

The format parameter is extracted before forwarding arguments to the implementation function.

Concurrency and Thread Safety

The tool is designed for multi-agent environments with the following safety mechanisms:

  1. POSIX File Locking: All read/write operations use fcntl.flock() through the _locked_file() context manager.
  1. Atomic Writes: _write_namespace() uses truncate() before json.dump() to ensure atomic replacement of file contents.
  1. Lazy Expiry: Expired entries are automatically skipped during read operations but not proactively cleaned unless accessed.

Storage Structure

Data persists in ~/.agent-memory/ with the following structure:

~/.agent-memory/
├── _meta.json          # Global statistics
├── default.json        # Default namespace
├── project_a.json      # Custom namespace
└── user_sessions.json  # Another namespace

Each namespace file contains a JSON array of entry objects:

[
  {
    "key": "user_preference",
    "value": "dark_mode",
    "created_at": "2024-01-15T10:30:00Z",
    "accessed_at": "2024-01-15T14:22:00Z",
    "expires_at": null,
    "access_count": 15
  }
]

Usage Examples

Basic Deletion

{
  "name": "memory_forget",
  "arguments": {
    "key": "session_token"
  }
}

Deletion from Specific Namespace

{
  "name": "memory_forget",
  "arguments": {
    "key": "temp_cache",
    "namespace": "user_123"
  }
}

Deletion with JSON Response

{
  "name": "memory_forget",
  "arguments": {
    "key": "old_preference",
    "namespace": "settings",
    "format": "json"
  }
}

Idempotency

The tool is idempotent—calling it multiple times with the same key produces consistent results:

  • First call with existing key: Success (entry deleted)
  • Subsequent calls with same key: Error (key not found)

This property makes the tool safe for retry logic in agent workflows.

ToolRelationshipPurpose
memory_rememberComplementStore new entries
memory_recallRead counterpartRetrieve entries before deletion
memory_clear_namespaceBulk deletionRemove all entries in namespace
memory_list_namespacesDiscoveryFind available namespaces

Configuration Constants

The tool operates within the constraints defined by these constants:

ConstantValuePurpose
DEFAULT_NAMESPACE"default"Fallback namespace if none specified
STORAGE_DIRPath.home() / ".agent-memory"Base storage directory
CHARACTER_LIMIT25,000Maximum response size before truncation
META_FILE"_meta.json"Global metadata filename

Source: https://github.com/Rumblingb/agent-memory-mcp / Human Manual

Doramagic Pitfall Log

Source-linked risks stay visible on the manual page so the preview does not read like a recommendation.

medium Project risk needs validation

The project should not be treated as fully validated until this signal is reviewed.

medium README/documentation is current enough for a first validation pass.

The project should not be treated as fully validated until this signal is reviewed.

medium Maintainer activity is unknown

Users cannot judge support quality until recent activity, releases, and issue response are checked.

medium no_demo

The project may affect permissions, credentials, data exposure, or host boundaries.

Doramagic Pitfall Log

Doramagic extracted 7 source-linked risk signals. Review them before installing or handing real data to the project.

1. Project risk: Project risk needs validation

  • Severity: medium
  • Finding: Project risk is backed by a source signal: Project risk needs validation. Treat it as a review item until the current version is checked.
  • User impact: The project should not be treated as fully validated until this signal is reviewed.
  • Recommended check: Open the linked source, confirm whether it still applies to the current version, and keep the first run isolated.
  • Evidence: identity.distribution | github_repo:1236240815 | https://github.com/Rumblingb/agent-memory-mcp | repo=agent-memory-mcp; install=mcp

2. Capability assumption: README/documentation is current enough for a first validation pass.

  • Severity: medium
  • Finding: README/documentation is current enough for a first validation pass.
  • User impact: The project should not be treated as fully validated until this signal is reviewed.
  • Recommended check: Open the linked source, confirm whether it still applies to the current version, and keep the first run isolated.
  • Evidence: capability.assumptions | github_repo:1236240815 | https://github.com/Rumblingb/agent-memory-mcp | README/documentation is current enough for a first validation pass.

3. Maintenance risk: Maintainer activity is unknown

  • Severity: medium
  • Finding: Maintenance risk is backed by a source signal: Maintainer activity is unknown. Treat it as a review item until the current version is checked.
  • User impact: Users cannot judge support quality until recent activity, releases, and issue response are checked.
  • Recommended check: Open the linked source, confirm whether it still applies to the current version, and keep the first run isolated.
  • Evidence: evidence.maintainer_signals | github_repo:1236240815 | https://github.com/Rumblingb/agent-memory-mcp | last_activity_observed missing

4. Security or permission risk: no_demo

  • Severity: medium
  • Finding: no_demo
  • User impact: The project may affect permissions, credentials, data exposure, or host boundaries.
  • Recommended check: Open the linked source, confirm whether it still applies to the current version, and keep the first run isolated.
  • Evidence: downstream_validation.risk_items | github_repo:1236240815 | https://github.com/Rumblingb/agent-memory-mcp | no_demo; severity=medium

5. Security or permission risk: no_demo

  • Severity: medium
  • Finding: no_demo
  • User impact: The project may affect permissions, credentials, data exposure, or host boundaries.
  • Recommended check: Open the linked source, confirm whether it still applies to the current version, and keep the first run isolated.
  • Evidence: risks.scoring_risks | github_repo:1236240815 | https://github.com/Rumblingb/agent-memory-mcp | no_demo; severity=medium

6. Maintenance risk: issue_or_pr_quality=unknown

  • Severity: low
  • Finding: issue_or_pr_quality=unknown。
  • User impact: Users cannot judge support quality until recent activity, releases, and issue response are checked.
  • Recommended check: Open the linked source, confirm whether it still applies to the current version, and keep the first run isolated.
  • Evidence: evidence.maintainer_signals | github_repo:1236240815 | https://github.com/Rumblingb/agent-memory-mcp | issue_or_pr_quality=unknown

7. Maintenance risk: release_recency=unknown

  • Severity: low
  • Finding: release_recency=unknown。
  • User impact: Users cannot judge support quality until recent activity, releases, and issue response are checked.
  • Recommended check: Open the linked source, confirm whether it still applies to the current version, and keep the first run isolated.
  • Evidence: evidence.maintainer_signals | github_repo:1236240815 | https://github.com/Rumblingb/agent-memory-mcp | release_recency=unknown

Source: Doramagic discovery, validation, and Project Pack records

Community Discussion Evidence

These external discussion links are review inputs, not standalone proof that the project is production-ready.

Sources 1

Count of project-level external discussion links exposed on this manual page.

Use Review before install

Open the linked issues or discussions before treating the pack as ready for your environment.

Community Discussion Evidence

Doramagic exposes project-level community discussion separately from official documentation. Review these links before using agent-memory-mcp with real data or production workflows.

Source: Project Pack community evidence and pitfall evidence