Doramagic Project Pack · Human Manual
agent-memory-mcp
Related topics: Installation, Quick Start Guide
Overview
Related topics: Installation, Quick Start Guide
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Related Pages
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 --> FSources: server.py
Storage Model
The system stores data as JSON files in ~/.agent-memory/, with one file per namespace plus a metadata file:
| File Pattern | Purpose |
|---|---|
{namespace}.json | Key-value entries for a specific namespace |
_meta.json | Global 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:
| Tool | Description | Read-Only | Destructive |
|---|---|---|---|
memory_remember | Store a value with optional TTL | No | No |
memory_recall | Retrieve a value + metadata | Yes | No |
memory_forget | Delete a key permanently | No | Yes |
memory_search | Search all namespaces by keyword | Yes | No |
memory_list_namespaces | List namespaces with counts | Yes | No |
memory_clear_namespace | Wipe a namespace | No | Yes |
memory_stats | Global storage statistics | Yes | No |
Sources: server.py:206-298
Memory Entry Schema
Each memory entry contains the following fields:
| Field | Type | Description |
|---|---|---|
key | string | Unique identifier within namespace |
value | string | Stored content (max ~25,000 characters) |
namespace | string | Storage partition identifier |
created_at | ISO timestamp | Creation timestamp |
accessed_at | ISO timestamp | Last access timestamp |
expires_at | ISO timestamp or null | TTL expiration time |
access_count | integer | Number 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
| Tier | Entries | Price | Features |
|---|---|---|---|
| Free | 1,000 | Free | All 7 tools |
| Pro | Unlimited | $19/month | Unlimited 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
| Attribute | Value |
|---|---|
| License | MIT |
| Author | Nous Research |
| Repository | github.com/nousresearch/agent-memory-mcp |
Sources: README.md
Sources: README.md
Installation
Related topics: Overview, Quick Start Guide
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Related Pages
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:
| Requirement | Version | Notes |
|---|---|---|
| Python | 3.10+ | Core runtime requirement |
| pip | Latest | For package installation |
| POSIX-compatible OS | Linux/macOS | Required for file locking via fcntl |
| MCP Client | Compatible version | Any 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
Method 1: pip (Recommended)
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:
| Package | Version | Purpose |
|---|---|---|
| mcp | ≥1.0.0 | Model 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:
| Variable | Default | Description |
|---|---|---|
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:
| Tool | Description |
|---|---|
memory_remember | Store a value with optional TTL |
memory_recall | Retrieve a value + metadata |
memory_forget | Delete a key permanently |
memory_search | Search all namespaces by keyword |
memory_list_namespaces | List namespaces with counts |
memory_clear_namespace | Wipe a namespace |
memory_stats | Global 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| BLicensing
Agent Memory MCP is distributed under the MIT License:
Sources: README.md:1
| Item | Value |
|---|---|
| License | MIT |
| Version | 1.0.0 |
| Author | Nous 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
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Related Pages
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| CInstallation
Prerequisites
| Requirement | Version |
|---|---|
| Python | 3.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 Namespace | Purpose |
|---|---|
default | General-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
| Tool | Purpose | Destructive | Read-Only |
|---|---|---|---|
memory_remember | Store a value | No | No |
memory_recall | Retrieve a value | No | Yes |
memory_forget | Delete a key | Yes | No |
memory_search | Search by keyword | No | Yes |
memory_list_namespaces | List all namespaces | No | Yes |
memory_clear_namespace | Wipe a namespace | Yes | No |
memory_stats | Get storage statistics | No | Yes |
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
}
| Parameter | Type | Required | Description |
|---|---|---|---|
key | string | Yes | Unique identifier for this memory entry |
value | string | Yes | Content to store |
namespace | string | No | Namespace name (default: default) |
ttl_seconds | integer | No | Auto-expiry duration in seconds |
format | string | No | markdown 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"
}
| Parameter | Type | Required | Description |
|---|---|---|---|
key | string | Yes | The key to retrieve |
namespace | string | No | Namespace to look in (default: default) |
format | string | No | markdown 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"
}
| Parameter | Type | Required | Description |
|---|---|---|---|
key | string | Yes | The key to delete |
namespace | string | No | Namespace to delete from (default: default) |
format | string | No | markdown 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
}
| Parameter | Type | Required | Description |
|---|---|---|---|
query | string | Yes | Search keyword or substring |
namespace | string | No | Limit search to specific namespace |
limit | integer | No | Maximum results (default: 10) |
format | string | No | markdown 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"
}
| Parameter | Type | Required | Description |
|---|---|---|---|
namespace | string | Yes | Namespace 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:
| Metric | Description |
|---|---|
total_entries | Total active memory entries |
total_size_bytes | Storage size in bytes |
total_size_human | Human-readable size |
namespace_count | Number of namespaces |
oldest_entry | Timestamp of oldest entry |
newest_entry | Timestamp of newest entry |
storage_path | Directory path (~/.agent-memory/) |
free_tier_limit | Free 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:
| Format | Use Case |
|---|---|
markdown | Human-readable output (default) |
json | Programmatic 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 --> PCommon 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 Condition | Response |
|---|---|
| Empty key | Key must not be empty |
| Expired entry | Key 'X' has expired |
| Key not found | Key 'X' not found in namespace 'Y' |
| Internal error | Internal error in {tool}: {exception} |
All errors return a response with isError: true.
Sources: server.py:95-105
Configuration Options
| Option | Default | Description |
|---|---|---|
STORAGE_DIR | ~/.agent-memory/ | Storage directory location |
DEFAULT_NAMESPACE | default | Fallback namespace |
CHARACTER_LIMIT | 25,000 | Max response truncation |
Sources: server.py:42-46
Next Steps
- Explore Advanced Search Patterns for fuzzy matching techniques
- Learn about Namespace Organization best practices
- Set up Monitoring with
memory_statsfor capacity planning - Configure Stripe Integration for Pro tier upgrades
Sources: requirements.txt:1
Namespaces
Related topics: TTL Management
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Related Pages
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:#fff9c4Storage Model
File Structure
Namespaces are persisted as individual JSON files in the ~/.agent-memory/ directory:
| Element | Path | Purpose |
|---|---|---|
| Namespace files | ~/.agent-memory/{namespace}.json | Individual key-value stores |
| Metadata file | ~/.agent-memory/_meta.json | Global 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:
| Parameter | Type | Default | Description |
|---|---|---|---|
key | string | *required* | Unique identifier for the memory entry |
value | string | *required* | Content to store |
namespace | string | "default" | Target namespace |
ttl_seconds | integer | null | Time-to-live in seconds (optional) |
format | string | "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:
| Parameter | Type | Default | Description |
|---|---|---|---|
key | string | *required* | Key to retrieve |
namespace | string | "default" | Namespace to search |
format | string | "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:
| Parameter | Type | Default | Description |
|---|---|---|---|
key | string | *required* | Key to delete |
namespace | string | "default" | Namespace to delete from |
format | string | "markdown" | Response format |
Sources: server.py:263-303
Memory Search
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:
| Parameter | Type | Default | Description |
|---|---|---|---|
query | string | *required* | Search keyword (case-insensitive) |
namespace | string | null | Limit search to one namespace (searches all if omitted) |
limit | integer | 10 | Maximum results to return |
format | string | "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:
| Field | Description |
|---|---|
namespace | Namespace name |
active_entries | Non-expired entries |
expired_entries | Entries 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:
| Parameter | Type | Default | Description |
|---|---|---|---|
namespace | string | *required* | Namespace to clear |
format | string | "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 + metadataThread 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"
)
Cross-Namespace Search
# 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:
| Tool | readOnlyHint | destructiveHint | idempotentHint |
|---|---|---|---|
memory_remember | false | false | true |
memory_recall | true | false | true |
memory_forget | false | true | true |
memory_search | true | false | true |
memory_list_namespaces | true | false | true |
memory_clear_namespace | false | true | true |
Sources: server.py:113-430
Constants
| Constant | Value | Description |
|---|---|---|
DEFAULT_NAMESPACE | "default" | Fallback namespace when none specified |
STORAGE_DIR | Path.home() / ".agent-memory" | Base directory for all data |
CHARACTER_LIMIT | 25,000 | Maximum 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
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Related Pages
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
| Component | Description |
|---|---|
entry.get("expires_at") | Retrieves the Unix timestamp when entry expires |
None return | No TTL set, entry never expires |
True result | Current 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,
}
| Parameter | Type | Description |
|---|---|---|
ttl_seconds | integer | Duration in seconds before auto-expiry |
expires_at | float or None | Unix 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
endThe lazy expiry approach offers several advantages:
- No background processes - Expiry is handled during normal operations
- Immediate cleanup - Expired entries are removed on next access
- 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
| Parameter | Required | Type | Default | Description |
|---|---|---|---|---|
key | Yes | string | - | Unique identifier for the memory entry |
value | Yes | string | - | Content to store |
namespace | No | string | "default" | Storage partition |
ttl_seconds | No | integer | None | Seconds until auto-expiry |
format | No | string | "markdown" | Response format (markdown or json) |
memory_recall
| Parameter | Required | Type | Default | Description |
|---|---|---|---|---|
key | Yes | string | - | Key to retrieve |
namespace | No | string | "default" | Namespace to search |
format | No | string | "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
}
| Field | Type | Description |
|---|---|---|
key | string | Unique identifier within namespace |
value | string | Stored content |
namespace | string | Logical partition identifier |
created_at | ISO8601 string | Creation timestamp |
accessed_at | ISO8601 string | Last access timestamp |
expires_at | Unix timestamp or null | Expiry time (null = never expires) |
access_count | integer | Number 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:
- Use
memory_recallto retrieve the current value - Use
memory_rememberwith a newttl_secondsvalue
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
| Operation | Expired Entry Behavior |
|---|---|
memory_remember | Ignored (new key/value) |
memory_recall | Deleted, returns error |
memory_forget | Not found (already cleaned) |
memory_search | Excluded from results |
memory_list_namespaces | Counted in expired_entries |
memory_stats | Excluded 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
- No automatic background cleanup - Expired entries persist in storage files until accessed
- No TTL modification - Existing entries cannot have their TTL updated; must recreate
- Second-level precision - TTL is calculated in seconds, not milliseconds
- 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...
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
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.
| Property | Value |
|---|---|
| Storage Location | ~/.agent-memory/ |
| File Format | JSON (one file per namespace) |
| Metadata File | _meta.json |
| Concurrency Model | POSIX file locking (fcntl) |
| Thread Safety | Yes (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 Class | Action | Result |
|---|---|---|
[a-zA-Z0-9_.\-] | Allowed | Preserved |
| All others | Replaced | _ (underscore) |
| Empty after sanitization | Fallback | default 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
}
| Field | Type | Description |
|---|---|---|
key | string | Unique identifier within namespace |
value | string | Stored content (any text) |
created_at | ISO 8601 string | Creation timestamp |
accessed_at | ISO 8601 string | Last access timestamp |
expires_at | ISO 8601 string or null | TTL expiry timestamp (null = never) |
access_count | integer | Number of times accessed |
Sources: server.py:189-195
Metadata File Structure (`_meta.json`)
{
"total_entries": 150,
"namespace_count": 5
}
| Field | Type | Description |
|---|---|---|
total_entries | integer | Total active (non-expired) entries |
namespace_count | integer | Number 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()
| Operation | Lock Type | Reason |
|---|---|---|
| Read | LOCK_EX (exclusive) | Ensures consistent read after write |
| Write | LOCK_EX (exclusive) | Prevents concurrent modifications |
os.fsync | After write | Guarantees 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:
- Truncate file first (removes old data)
- Write complete JSON array
- Flush to kernel buffer
- 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:
| Event | Action |
|---|---|
memory_recall | Check expiry, delete if expired |
memory_search | Skip expired entries (no deletion) |
memory_forget | No expiry check needed |
| Background cleanup | None (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:
| Stat | Description |
|---|---|
total_entries | Sum of active entries across all namespaces |
total_size_bytes | Disk usage in bytes |
total_size_human | Human-readable size (B, KB, MB, GB, TB) |
namespace_count | Number of namespace files |
oldest_entry | ISO timestamp of earliest entry |
newest_entry | ISO timestamp of most recent entry |
storage_path | Filesystem path to storage directory |
free_tier_limit | 1000 entries |
pro_tier_limit | unlimited |
Sources: server.py:271-307
Access Tracking
Every memory entry maintains access metadata:
| Field | Purpose | Updated When |
|---|---|---|
created_at | Creation timestamp | Entry creation |
accessed_at | Last access timestamp | memory_recall |
access_count | Total access count | memory_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:
| Feature | Behavior |
|---|---|
| Separate files | Each namespace has its own .json file |
| Independent entries | Keys only unique within namespace |
| Independent TTLs | Each entry has its own expiry |
| Independent access | Separate metadata per entry |
Search behavior by namespace:
| Parameter | Search Scope |
|---|---|
namespace omitted | All namespaces |
namespace specified | Single 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 Condition | Handling |
|---|---|
| Malformed JSON file | Return empty list, log error |
| Empty file | Return empty list |
| Missing namespace | Create empty file on write |
| Lock acquisition failure | Block until lock available |
| Disk full | OS-level error propagated |
Performance Characteristics
| Aspect | Value/Note |
|---|---|
| File I/O | Blocking (synchronous) |
| Lock scope | Per-operation (not transaction) |
| Search complexity | O(n) across namespaces |
| Memory per namespace | Proportional to entries |
| Disk I/O per access | 1 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
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Related Pages
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
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
key | string | Yes | — | Unique identifier for the memory entry |
value | string | Yes | — | The content/value to store persistently |
namespace | string | No | "default" | Logical container for organizing entries |
ttl_seconds | integer | No | null | Time-to-live in seconds; entry auto-expires after this duration |
format | string | No | "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:
| Aspect | Specification |
|---|---|
| Storage Location | ~/.agent-memory/ |
| File Format | One JSON file per namespace |
| Namespace File Naming | Sanitized: [^a-zA-Z0-9_.\-] → _ |
| Locking Mechanism | POSIX fcntl.flock() |
| Atomic Writes | Truncate-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
- Input Validation: Reject empty or whitespace-only keys
- Lock Acquisition: Obtain exclusive POSIX file lock on namespace file
- Entry Creation: Build entry dict with timestamps and TTL calculation
- File Write: Atomically write updated entries array
- Metadata Update: Recalculate global entry counts via
_recalc_meta() - 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_secondsat write time - Expired entries are lazily deleted during
memory_recalloperations - TTL precision is second-level (not millisecond)
- A value of
nullor omittingttl_secondscreates 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] --> NCNamespace 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:
| Annotation | Value | Meaning |
|---|---|---|
readOnlyHint | false | Modifies server state |
destructiveHint | false | Does not delete existing data |
idempotentHint | true | Safe to retry |
openWorldHint | false | Operates 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"
}
Related Tools
| Tool | Purpose | Relationship |
|---|---|---|
memory_recall | Retrieve stored values by key | Complements write with read |
memory_forget | Delete a specific entry | Inverse operation |
memory_search | Find entries by keyword | Discovery of stored data |
memory_list_namespaces | List all namespaces | Namespace enumeration |
memory_stats | View storage statistics | Global 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
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Related Pages
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:
| Attribute | Value |
|---|---|
| Tool Name | memory_recall |
| Category | Read operation |
| Destructive | No |
| Idempotent | Yes |
| Default Namespace | default |
| Response Formats | markdown, json |
Sources: server.py
Function Signature
def memory_recall(
key: str,
namespace: str = DEFAULT_NAMESPACE,
fmt: Optional[str] = None,
) -> str:
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
key | string | Yes | — | The unique key identifying the memory entry to retrieve |
namespace | string | No | "default" | The namespace to search within |
fmt | string | No | "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:
| Field | Purpose |
|---|---|
expires_at | ISO timestamp when entry expires |
_is_expired(entry) | Returns True if current time > expires_at |
When an entry expires:
- The entry is removed from the namespace file immediately
- Global metadata is recalculated
- 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:
| Field | Type | Description |
|---|---|---|
accessed_at | ISO string | Timestamp of most recent access |
access_count | integer | Total 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 Condition | Message |
|---|---|
| Empty key | Key must not be empty |
| Key not found | Key 'example_key' not found in namespace 'default' |
| Entry expired | Key '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 Tool | Interaction |
|---|---|
memory_remember | Stores data that memory_recall retrieves |
memory_search | Uses access_count to rank search results |
memory_forget | Permanently deletes entries |
memory_stats | Tracks 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
| Scenario | Handling |
|---|---|
| Empty key | Returns error immediately before file I/O |
| Non-existent namespace | Returns empty array; treated as "key not found" |
| Corrupted JSON file | Returns empty array, log error |
| Expired entry | Removes from file, returns expiration error |
| File permission errors | Caught 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
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Related Pages
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 Name | Purpose | Destructive | Read-Only |
|---|---|---|---|
| memory_remember | Store a key-value pair | No | No |
| memory_recall | Retrieve a value by key | No | Yes |
| memory_forget | Delete a key permanently | Yes | No |
| memory_search | Search across namespaces | No | Yes |
| memory_list_namespaces | List all namespaces | No | Yes |
| memory_clear_namespace | Wipe entire namespace | Yes | No |
| memory_stats | Get storage statistics | No | Yes |
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:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| key | string | Yes | — | Unique key identifying the memory entry to delete |
| namespace | string | No | "default" | Namespace containing the entry |
| format | string | No | "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:
| Annotation | Value | Implication |
|---|---|---|
| readOnlyHint | false | This tool modifies state |
| destructiveHint | true | This operation permanently removes data |
| idempotentHint | true | Multiple calls with same key produce same result (success or not-found) |
| openWorldHint | false | Only 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
- Input Validation: Checks that the key is not empty or whitespace-only. Empty keys return an error immediately.
- Read Namespace: Loads all entries from the namespace's JSON file using the thread-safe
_read_namespace()helper.
- Search for Entry: Iterates through entries to find a matching key.
- Remove Entry: If found, removes the entry from the list using
pop(i).
- Write Back: Persists the modified entries list to the namespace file using
_write_namespace().
- Update Metadata: Calls
_recalc_meta()to update global statistics reflecting the entry count change.
- 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
| Scenario | Error Message | HTTP-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:
- POSIX File Locking: All read/write operations use
fcntl.flock()through the_locked_file()context manager.
- Atomic Writes:
_write_namespace()usestruncate()beforejson.dump()to ensure atomic replacement of file contents.
- 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.
Related Tools
| Tool | Relationship | Purpose |
|---|---|---|
| memory_remember | Complement | Store new entries |
| memory_recall | Read counterpart | Retrieve entries before deletion |
| memory_clear_namespace | Bulk deletion | Remove all entries in namespace |
| memory_list_namespaces | Discovery | Find available namespaces |
Configuration Constants
The tool operates within the constraints defined by these constants:
| Constant | Value | Purpose |
|---|---|---|
| DEFAULT_NAMESPACE | "default" | Fallback namespace if none specified |
| STORAGE_DIR | Path.home() / ".agent-memory" | Base storage directory |
| CHARACTER_LIMIT | 25,000 | Maximum response size before truncation |
| META_FILE | "_meta.json" | Global metadata filename |
Source: https://github.com/Rumblingb/agent-memory-mcp / Human Manual
memory_search Tool
Related topics: Namespaces, Quick Start Guide
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Related Pages
Related topics: Namespaces, Quick Start Guide
memory_search Tool
The memory_search tool is a fuzzy keyword search capability within the Agent Memory MCP server that enables AI agents to retrieve stored memory entries across one or all namespaces using case-insensitive substring matching. It serves as the primary discovery mechanism for agents seeking to recall previously stored context, preferences, or state information without requiring exact key names.
Overview
Agent Memory MCP is a persistent key-value memory store built on the Model Context Protocol (MCP) Python SDK. The memory_search tool addresses a fundamental limitation in agent-based systems: the inability to retrieve information when the exact storage key is unknown. Rather than requiring agents to remember precise key names, the search tool accepts natural language queries and matches them against both keys and values stored across all memory namespaces.
The tool implements substring matching at the byte-string level, converting both the search query and stored content to lowercase before comparison. This design choice prioritizes recall over precision, ensuring that partial matches, typos, and variations in casing do not prevent agents from finding relevant information.
Architecture
System Context
graph TD
A["Agent / Client"] -->|MCP Tool Call| B["memory_search"]
B --> C{namespace param?}
C -->|Yes| D[Search Single Namespace]
C -->|No| E[Discover All Namespaces]
E --> F["Glob *.json in ~/.agent-memory/"]
F --> G["Exclude _meta.json"]
D --> H["Read Namespace JSON Files"]
G --> H
H --> I["Parse entries array"]
I --> J{Entry expired?}
J -->|Yes| K[Skip entry]
J -->|No| L{Check match conditions}
L --> M["query ∈ key.lower()"]
L --> N["query ∈ value.lower()"]
M -->|OR| O[Add to results]
N -->|OR| O
O --> P["Sort: access_count DESC, created_at DESC"]
P --> Q["Apply limit"]
Q --> R["Truncate values to 500 chars"]
R --> S[Format response]
S --> T["Return markdown or JSON"]
K --> PData Model
Each memory entry stored in the Agent Memory system follows a consistent schema:
{
"key": "string",
"value": "string",
"created_at": "ISO 8601 timestamp",
"accessed_at": "ISO 8601 timestamp",
"expires_at": "ISO 8601 timestamp | null",
"access_count": "integer"
}
The memory_search tool operates on this entry structure, examining both key and value fields for substring matches while respecting the expires_at field for TTL-based entries. Sources: server.py
Function Signature
def memory_search(
query: str,
namespace: Optional[str] = None,
limit: int = 10,
fmt: Optional[str] = None,
) -> str:
The function returns a formatted string response rather than a structured object, supporting both markdown and JSON output formats through the fmt parameter.
Parameters
| Parameter | Type | Required | Default | Description | |
|---|---|---|---|---|---|
query | string | Yes | — | Search keyword or substring to match against keys and values | |
namespace | `string \ | null` | No | null | Optional namespace to limit search scope. When null, searches all namespaces |
limit | integer | No | 10 | Maximum number of results to return | |
fmt | `string \ | null` | No | null | Response format: "markdown" or "json". Defaults to markdown when null |
Search Algorithm
Step 1: Input Validation
The search immediately rejects empty queries to prevent unnecessary filesystem operations:
if not query.strip():
return _error("Query must not be empty", fmt)
This validation ensures that whitespace-only strings and empty inputs are rejected before any namespace discovery occurs. Sources: server.py
Step 2: Query Normalization
The search query is normalized to lowercase for case-insensitive matching:
q = query.lower()
This single transformation enables matching against both key.lower() and value.lower() without additional case-handling logic.
Step 3: Namespace Discovery
When no namespace is specified, the tool discovers all available namespaces by globbing the storage directory:
namespaces_to_search = [
p.stem
for p in STORAGE_DIR.glob("*.json")
if p.stem != "_meta"
]
The _meta.json file is explicitly excluded from search results as it contains internal metadata rather than user data. Each discovered namespace corresponds to a namespace.json file in ~/.agent-memory/. Sources: server.py
Step 4: Entry Matching
For each namespace and entry, the matching logic follows this flow:
graph LR
A[entry.key.lower()] --> C{query in key?}
B[entry.value.lower()] --> D{query in value?}
C -->|Yes| E[Include result]
D -->|Yes| E
C -->|No| F[Skip]
D -->|No| FThe OR condition means an entry is included if the query appears in either the key or the value. Expired entries are automatically skipped:
if _is_expired(entry):
continue
Step 5: Result Truncation
To prevent oversized responses, value fields are truncated to 500 characters before inclusion in results:
results.append(
{
"namespace": ns,
"key": entry["key"],
"value": _truncate(entry["value"], 500),
"created_at": entry["created_at"],
"access_count": entry.get("access_count", 0),
}
)
The full value remains intact in storage; truncation occurs only in search results.
Step 6: Result Ordering
Results are sorted by two criteria in descending order:
access_count— More frequently accessed entries rank highercreated_at— More recently created entries rank higher within equal access counts
This ordering heuristic surfaces the most relevant and useful memories based on historical access patterns. Sources: server.py
MCP Tool Definition
The memory_search tool is registered with the MCP server with the following schema definition:
Tool(
name="memory_search",
description="Search memories across namespaces by keyword substring. Case-insensitive match on both keys and values. Returns results sorted by access count.",
inputSchema={
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Search keyword or substring.",
},
"namespace": {
"type": "string",
"description": "Optional namespace to limit search. Searches ALL namespaces if omitted.",
},
"limit": {
"type": "integer",
"description": "Maximum results to return (default: 10).",
"default": 10,
},
"format": {
"type": "string",
"enum": ["markdown", "json"],
"description": "Response format (default: markdown).",
"default": "markdown",
},
},
"required": ["query"],
},
annotations=ToolAnnotations(
readOnlyHint=True,
destructiveHint=False,
idempotentHint=True,
openWorldHint=True,
),
),
Tool Annotations
| Annotation | Value | Implication |
|---|---|---|
readOnlyHint | True | The tool does not modify stored data |
destructiveHint | False | No permanent deletion occurs during search |
idempotentHint | True | Multiple identical calls produce identical results |
openWorldHint | True | Search may cross namespace boundaries |
These annotations inform client applications about the nature of the operation, enabling appropriate caching, undo handling, and UI behavior.
Response Formats
Markdown Output (Default)
When fmt="markdown" or fmt is None:
## ✅ Success
**query:** search_term
**results:** 3 items
- **namespace:** project-alpha
**key:** api_credentials
**value:** sk-... (truncated to 500 chars)
**created_at:** 2025-01-15T10:30:00
**access_count:** 42
- **namespace:** project-alpha
**key:** user_preferences
**value:** dark_mode: true, language:...
**created_at:** 2025-01-14T08:15:00
**access_count:** 38
- **namespace:** default
**key:** search_history
**value:** previous queries stored here...
**created_at:** 2025-01-10T14:22:00
**access_count:** 12
JSON Output
When fmt="json":
{
"status": "ok",
"query": "search_term",
"results": [
{
"namespace": "project-alpha",
"key": "api_credentials",
"value": "sk-... (truncated)",
"created_at": "2025-01-15T10:30:00",
"access_count": 42
}
],
"total_found": 3,
"returned": 3
}
Usage Examples
Basic Search Across All Namespaces
result = memory_search(query="api_key", limit=5)
This searches all namespaces for entries containing "api_key" in either the key or value field, returning the top 5 matches sorted by access frequency.
Namespace-Scoped Search
result = memory_search(
query="user",
namespace="session_2024",
limit=20
)
Limiting search to a specific namespace reduces I/O and improves response time when the agent knows the relevant context domain.
Integration with MCP Client
from mcp import ClientSession
async def search_memories(session: ClientSession, query: str):
result = await session.call_tool(
"memory_search",
arguments={
"query": query,
"namespace": None,
"limit": 10,
"format": "markdown"
}
)
return result.content[0].text
Error Handling
Empty Query Error
if not query.strip():
return _error("Query must not be empty", fmt)
Returns:
## ❌ Error
**Query must not be empty**
Internal Error Handling
The call_tool wrapper catches exceptions and formats them as error responses:
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,
)
This ensures that filesystem errors, JSON parsing failures, or unexpected exceptions return structured error responses rather than crashes.
Performance Considerations
| Factor | Impact |
|---|---|
| Namespace count | Linear increase in filesystem reads |
| Entries per namespace | Linear scan through all entries |
| Value size | Truncation mitigates large value impact |
| Default limit (10) | Bounds result set size |
The tool does not implement indexing; every search performs a full scan of matching entries. For deployments with thousands of entries across many namespaces, consider:
- Using namespace-scoped searches when context is known
- Increasing the
limitparameter to reduce missed relevant results - Partitioning memories into task-specific namespaces
Related Tools
| Tool | Purpose |
|---|---|
memory_remember | Store new key-value entries |
memory_recall | Retrieve entry by exact key name |
memory_forget | Delete entry by key |
memory_list_namespaces | Enumerate available namespaces |
memory_clear_namespace | Delete all entries in a namespace |
memory_stats | View storage statistics |
Conclusion
The memory_search tool provides essential discovery capabilities for AI agents using Agent Memory MCP. By supporting fuzzy substring matching across both keys and values, it enables agents to find relevant context without requiring perfect recall of storage locations. The tool's design balances functionality with simplicity: case-insensitive matching, configurable result limits, and output formatting options make it adaptable to diverse agent architectures while maintaining predictable behavior through filesystem-based storage.
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.
The project should not be treated as fully validated until this signal is reviewed.
The project should not be treated as fully validated until this signal is reviewed.
Users cannot judge support quality until recent activity, releases, and issue response are checked.
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.
Count of project-level external discussion links exposed on this manual page.
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.
- Project risk needs validation - GitHub / issue
Source: Project Pack community evidence and pitfall evidence