Skip to main content
The Remember Plugin provides encrypted session memory for FrontMCP servers, enabling AI agents to remember context across conversations.

Why Use Remember?

Session Memory

Store user preferences, conversation context, and state across tool invocations

Encrypted Storage

AES-256-GCM encryption protects sensitive data at rest

Multiple Backends

Redis, Vercel KV, or in-memory storage for different deployment needs

Scoped Storage

Organize data by session, user, tool, or global scope
For tool approval workflows (Claude Code-style permissions), see the Approval Plugin.

Installation

How It Works

1

Context Extension

The plugin adds this.remember to all execution contexts (ToolContext, AgentContext, etc.)
2

Scoped Storage

Data is organized by scope (session, user, tool, global) with automatic key prefixing
3

Encryption

Values are encrypted before storage using keys derived from session/user identifiers
4

TTL Management

Entries expire automatically based on configured TTL or scope lifetime
All stored values are encrypted by default using AES-256-GCM. Keys are derived using HKDF-SHA256 from session and user identifiers.

Quick Start

Basic Setup

Using Memory in Tools

The plugin extends all execution contexts with this.remember:

Memory Scopes

Memory is organized into four scopes with different visibility and lifetime:
session, tool, and user scopes require a per-client identity.session and tool memory belong to the session the server verified for the request, never to an mcp-session-id the client merely sends. A request without a verified session has no session identity: a stateless HTTP transport injects the same session id (__stateless__) into every request, MCP 2026-07-28 has no sessions, and an mcp-session-id the server did not verify could be anyone’s. session and tool scope then fall back to the authenticated principal (the memory lasts across that principal’s requests), and an unauthenticated request without a verified session is refused with a RememberIdentityError rather than being given a namespace it would share with other clients. user scope is likewise refused when there is no authenticated user, and an anonymous subject counts as none: the anon:<id> client id the SDK gives an anonymous caller is made up for one session (or for each request without one), so user memory kept under it would never be found again. The refusal reaches the client as its message (RememberIdentityError is a public MCP error, code REMEMBER_IDENTITY_REQUIRED), in production too.If the data really is shared across clients, say so with scope: 'global'. Otherwise authenticate the request or use a stateful transport.

Using Scopes


Data Structure

Entry Format

Each stored value is wrapped in an entry with metadata:

Branded Payloads

Use brands to categorize stored data semantically:
Brands enable filtering and batch operations by category:

Storage Options

In-Memory (Default)

Best for: Single-instance deployments, development, non-persistent data
Memory storage resets when the process restarts. Not shared across instances, and not shared between servers in one process either: every server gets its own store, even when several servers are built from the same app class or RememberPlugin.init() result, so a global value one server stores is not visible to another.
Best for: Multi-instance deployments, persistent memory, production

Vercel KV

Best for: Vercel deployments, serverless environments

Global Store

Use the store configuration from @FrontMcp decorator:

Encryption

All stored values are encrypted by default using AES-256-GCM.

How Keys Are Derived

  1. A master secret is derived from the REMEMBER_SECRET environment variable (or auto-generated and persisted)
  2. Per-entry keys are derived using HKDF-SHA256 over that secret plus the scope’s identity
  3. Each entry gets a unique key based on its scope and identity
Every scope mixes in the master secret, session and tool included. A session id is not a secret — the client knows it and it travels in the mcp-session-id header — so a key derived from it alone could be recomputed by anyone who had seen it.
Because the key now depends on the master secret, every instance sharing a store must use the same REMEMBER_SECRET. Without it each instance auto-generates its own and cannot decrypt entries written by the others.

Upgrading past the key-derivation change

Two changes moved data written by earlier versions:
  • Key derivation. session and tool keys now mix in the master secret, so ciphertext written before the change no longer decrypts.
  • Namespace encoding. session, tool and user prefixes now percent-encode every variable component, so an identity containing a character encodeURIComponent escapes — a : in a user id, for instance — now lives under a different key.
Both failures used to be silent: decryption returns null and a moved key simply misses, so the value reads as absent rather than raising. These three scopes are therefore stored under a v2: segment (remember:v2:session:<identity>:<key>), and the plugin purges the pre-v2 entries automatically, logging a warning with the number removed. The version segment is what makes that safe: a purge pattern of remember:session:* cannot match a live remember:v2:session:* key, not even one written concurrently by another instance. global is not versioned and is not purged — neither its keys nor its key derivation changed.

When the purge runs

Twenty-four hours after the fleet first reached the v2: layout — not after this process started — and never on the request path. The first instance to reach the store stamps a marker at <keyPrefix>__layout__:
Every instance reads that marker, and sweeps only once it is older than the window; until then it re-arms for the remainder. Keeping the clock in the store rather than in the process is what makes the window mean something. A process-local timer restarts on every deploy and every crash, so it never converges on “the fleet has been on v2: for a while”, and an instance that booted early would fire on its own schedule no matter when the last old instance drained. The marker is written once and never overwritten, so a later instance cannot reset it, and whichever instance is alive when the window passes completes the sweep. The window has to outlast two things: the rollout, and the period in which a bad deploy gets rolled back. The second is the one that matters — a rollback after the sweep makes the old fleet permanent again with its memory already deleted. Waiting costs nothing, because the entries are unreadable the whole time. Tune it with legacyPurgeDelayMs:
Serverless and edge runtimes. An invocation that ends before the timer fires never purges, and the timer is unreferenced so it will not hold a process open. That is the safe outcome — the entries are inert either way. Clear them with the commands below if you want the storage back.
If the marker cannot be read or written — a permission error, or a value that is not the expected JSON — the purge stands down and deletes nothing. It never treats a missing clock as licence to delete.
If you would rather migrate the data yourself, opt out entirely:

Secret Persistence

In development, the plugin automatically generates and persists an encryption secret to .frontmcp/remember-secret.json:
This enables consistent encryption across process restarts during development without manual configuration.
In production, set the REMEMBER_SECRET environment variable instead:
File-based persistence is disabled in production by default for security. If none of REMEMBER_SECRET, MCP_MEMORY_SECRET or MCP_SESSION_SECRET is set there, the plugin falls back to a random in-memory secret and logs a warning once: encrypted memory is then unreadable after a restart and by any other instance that shares the store.

Configuration


API Reference

RememberAccessor Methods

Promise<void>
Store a value with optional scope, TTL, and metadata
Promise<T | undefined>
Retrieve a value with optional default
Promise<RememberEntry<T> | undefined>
Retrieve the full entry including metadata, timestamps, and brand
Promise<boolean>
Update an existing value while preserving metadata. Returns false if key doesn’t exist.
Promise<boolean>
Check if a key exists
Promise<void>
Delete a key
Promise<string[]>
List keys with optional pattern matching

LLM-Accessible Tools

Enable built-in tools that let the LLM manage memory directly:
This exposes memory_remember_this, memory_recall, memory_forget, and memory_list_memories. Without prefix they are remember_this, recall, forget and list_memories; with enabled unset or false no memory tool is registered. Each tool’s description names its prefixed siblings, and a scope outside allowedScopes is rejected with a public REMEMBER_SCOPE_NOT_ALLOWED error (HTTP 400) whose message lists the allowed scopes, so the model can retry. That includes the default session scope when a call omits scope and allowedScopes does not list it; the refusal used to surface as Internal FrontMCP error in production. Each takes an optional scope (default session), described to the model the same way in all four tools: An anonymous caller cannot use user, nor session or tool without a session. None of these lifetimes is “until disconnect” or “forever”: entries last until they are forgotten or their ttl runs out, and session memory without a session belongs to the signed-in caller rather than ending with a connection.

Best Practices

  • Session: Temporary data, current conversation context
  • User: Preferences, settings that should persist
  • Tool: Tool-specific cache to avoid scope pollution
  • Global: Only for true application-wide settings
Don’t store sensitive data forever:
Redis provides:
  • Persistence across restarts
  • Sharing across multiple server instances
  • Better memory management with eviction policies
In development, the secret is auto-generated and stored in .frontmcp/remember-secret.json. Add this file to .gitignore.

Complete Example


Source Code

View the remember plugin source code

Approval Plugin

For tool authorization workflows

Plugin Guide

Learn more about FrontMCP plugins

Cache Plugin

For tool response caching