DEV Community

Preecha
Preecha

Posted on

How to Add Persistent Memory to Any AI Agent (So It Remembers Yesterday)

TL;DR

Add persistent memory to AI agents in four steps: (1) set up an MCP memory server with remember, recall, search, and rollback tools; (2) add memory instructions to agent prompts; (3) configure ~/.claude/settings.json for Claude Code or .cursor/mcp.json for Cursor; and (4) use memory patterns for decision logging, agent handoffs, and session checkpoints. Agents retain context across sessions, so you can stop copy-pasting previous conversations.

Try Apidog today

Persistent MCP memory solves the “I don’t remember yesterday” problem. Your agents can store decisions, deliverables, and project context, then retrieve them in a later session.

Typical workflow without persistent memory:

Day 1: "Build the user authentication system"
Agent: Builds JWT auth, creates a users table, implements refresh tokens.

Day 2: "Continue from yesterday"
Agent: "I don't have context from previous sessions. Can you paste what we did?"
Enter fullscreen mode Exit fullscreen mode

You paste the previous conversation, the agent reads thousands of lines of context, and you spend time rebuilding shared understanding.

With MCP memory, the agent stores useful project context as it works and recalls it when needed. This works for single-agent sessions and multi-agent workflows, such as handing backend decisions to a frontend agent while building APIs with Apidog integration.

What Is MCP Memory?

MCP memory lets AI agents store and retrieve information across sessions. Think of it as a shared notebook that agents can write to and read from.

Tool Purpose Example
remember Store information with tags Save “users table with UUID, bcrypt”
recall Search by keyword or agent Find “auth decisions”
search Find memories by tags Find all ecommerce bugs
rollback Restore a previous state Undo bad schema changes
┌─────────────────┐         ┌──────────────────┐         ┌─────────────┐
│  AI Agent       │         │  MCP Memory      │         │  Storage    │
│  (Claude Code)  │◄───────►│  Server          │◄───────►│  (SQLite)   │
└─────────────────┘   JSON  └──────────────────┘  I/O    └─────────────┘
Enter fullscreen mode Exit fullscreen mode

Step 1: Set Up an MCP Memory Server

You need an MCP server that exposes memory tools. You can use an existing implementation or run a local server.

Option A: Use a hosted memory server

npm install -g @example/mcp-memory-server
Enter fullscreen mode Exit fullscreen mode

Option B: Run a simple local server

Install the dependencies:

npm install @modelcontextprotocol/sdk zod
Enter fullscreen mode Exit fullscreen mode

Create memory-server.js:

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import fs from "fs/promises";
import path from "path";

const MEMORY_FILE = path.join(
  process.env.HOME,
  ".mcp-memory",
  "memories.json"
);

const server = new McpServer({
  name: "memory",
  version: "1.0.0"
});

// Ensure the memory file exists before any tool reads or writes it.
async function initMemory() {
  await fs.mkdir(path.dirname(MEMORY_FILE), { recursive: true });

  try {
    await fs.access(MEMORY_FILE);
  } catch {
    await fs.writeFile(MEMORY_FILE, JSON.stringify([]));
  }
}

// Store a memory entry.
server.tool(
  "remember",
  {
    content: z.string().describe("Information to store"),
    tags: z
      .array(z.string())
      .describe("Tags for retrieval, for example ['backend', 'auth']"),
    agent: z.string().optional().describe("Agent name for tagging")
  },
  async ({ content, tags, agent }) => {
    await initMemory();

    const memories = JSON.parse(
      await fs.readFile(MEMORY_FILE, "utf-8")
    );

    const memory = {
      id: Date.now().toString(),
      content,
      tags,
      agent,
      timestamp: new Date().toISOString()
    };

    memories.push(memory);

    await fs.writeFile(
      MEMORY_FILE,
      JSON.stringify(memories, null, 2)
    );

    return {
      content: [
        {
          type: "text",
          text: `Stored memory with tags: ${tags.join(", ")}`
        }
      ]
    };
  }
);

// Find memories by text or tag, optionally scoped to an agent.
server.tool(
  "recall",
  {
    query: z.string().describe("Search query or tag to find"),
    agent: z.string().optional().describe("Filter by agent name")
  },
  async ({ query, agent }) => {
    await initMemory();

    const memories = JSON.parse(
      await fs.readFile(MEMORY_FILE, "utf-8")
    );

    const results = memories.filter((memory) => {
      const matchesQuery =
        memory.content.toLowerCase().includes(query.toLowerCase()) ||
        memory.tags.some((tag) =>
          tag.toLowerCase().includes(query.toLowerCase())
        );

      const matchesAgent = !agent || memory.agent === agent;

      return matchesQuery && matchesAgent;
    });

    return {
      content: [
        {
          type: "text",
          text:
            results.length === 0
              ? "No memories found"
              : results
                  .map(
                    (memory) =>
                      `[${memory.timestamp}] ${memory.content}`
                  )
                  .join("\n\n")
        }
      ]
    };
  }
);

// Find memories by one or more exact tags.
server.tool(
  "search",
  {
    tags: z.array(z.string()).describe("Tags to search for"),
    limit: z.number().optional().default(10)
  },
  async ({ tags, limit }) => {
    await initMemory();

    const memories = JSON.parse(
      await fs.readFile(MEMORY_FILE, "utf-8")
    );

    const results = memories
      .filter((memory) =>
        tags.some((tag) => memory.tags.includes(tag))
      )
      .slice(0, limit);

    return {
      content: [
        {
          type: "text",
          text: results
            .map(
              (memory) =>
                `[${memory.agent || "unknown"}] ${memory.content}`
            )
            .join("\n\n")
        }
      ]
    };
  }
);

// Remove memories for an agent that were created after a timestamp.
server.tool(
  "rollback",
  {
    agent: z.string().describe("Agent name to roll back"),
    timestamp: z.string().describe("Roll back to this timestamp")
  },
  async ({ agent, timestamp }) => {
    await initMemory();

    const memories = JSON.parse(
      await fs.readFile(MEMORY_FILE, "utf-8")
    );

    const rolledBack = memories.filter(
      (memory) =>
        memory.agent !== agent ||
        new Date(memory.timestamp) <= new Date(timestamp)
    );

    await fs.writeFile(
      MEMORY_FILE,
      JSON.stringify(rolledBack, null, 2)
    );

    return {
      content: [
        {
          type: "text",
          text: `Rolled back ${agent} to ${timestamp}`
        }
      ]
    };
  }
);

const transport = new StdioServerTransport();

await server.connect(transport);
Enter fullscreen mode Exit fullscreen mode

Start the server:

node memory-server.js
Enter fullscreen mode Exit fullscreen mode

The server stores memories in:

~/.mcp-memory/memories.json
Enter fullscreen mode Exit fullscreen mode

Step 2: Add Memory Instructions to Your Agent Prompt

You do not need to modify individual agent files. Give the agent clear instructions for when to read and write memory.

Use a prompt like this:

You have access to MCP memory tools: remember, recall, search, rollback.

Follow these memory protocols:

When you start a session:
1. Recall context:
   recall(query="ecommerce-api", agent="Backend Architect")
2. Review pending items from the last session.

When you complete work:
1. Store deliverables with tags:
   remember(
     content="Created users table with UUID primary key, bcrypt password hashing, JWT auth with refresh tokens",
     tags=["ecommerce-api", "database", "auth"],
     agent="Backend Architect"
   )
2. Include decisions made and pending items.

When handing off to another agent:
1. Store context for the receiving agent:
   remember(
     content="API endpoints: /auth/login, /auth/register, /products, /orders. Auth flow: JWT access token (15min) + refresh token (7 days). Known issue: rate limiting not yet implemented",
     tags=["ecommerce-api", "handoff"],
     agent="Backend Architect"
   )

When something fails:
1. Search for the last known-good state.
2. Use rollback to restore it when appropriate.
Enter fullscreen mode Exit fullscreen mode

Example: Resume a backend session

Start the first session:

You: Activate Backend Architect mode. Design the user system for our e-commerce API.
Enter fullscreen mode Exit fullscreen mode

After the agent designs the system and implements authentication, it stores a summary:

remember(
  content="Users table with UUID primary keys, bcrypt password hashing, JWT auth, and refresh tokens",
  tags=["ecommerce-api", "auth", "database"],
  agent="Backend Architect"
)
Enter fullscreen mode Exit fullscreen mode

On the next day, continue with:

You: Continue from yesterday.
Enter fullscreen mode Exit fullscreen mode

The agent retrieves the saved context:

recall(query="ecommerce-api")
Enter fullscreen mode Exit fullscreen mode

Then it can respond with actionable context:

Yesterday I designed the users table with UUID primary keys, implemented bcrypt password hashing, and set up JWT authentication with refresh tokens.

Next step: product catalog schema.
Enter fullscreen mode Exit fullscreen mode

Step 3: Configure Claude Code

Add the memory server to your MCP configuration.

Edit ~/.claude/settings.json:

{
  "mcpServers": {
    "memory": {
      "command": "node",
      "args": ["/absolute/path/to/memory-server.js"],
      "env": {
        "HOME": "/Users/your-username"
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Restart Claude Code so it loads the new MCP server.

Test the connection:

  1. Use remember to store:
   Test memory for ecommerce project
Enter fullscreen mode Exit fullscreen mode
  1. Use these tags:
   ["test", "ecommerce-api"]
Enter fullscreen mode Exit fullscreen mode
  1. Use recall to search for:
   test
Enter fullscreen mode Exit fullscreen mode

If the recall result includes the stored text, the integration is working.

Step 4: Configure Cursor

Create .cursor/mcp.json in your project:

{
  "mcpServers": {
    "memory": {
      "command": "node",
      "args": ["/absolute/path/to/memory-server.js"]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Test the server from Cursor:

@memory remember "Starting ecommerce API project with PostgreSQL"
Enter fullscreen mode Exit fullscreen mode

Add tags:

["ecommerce-api", "setup"]
Enter fullscreen mode Exit fullscreen mode

Then recall the project context:

@memory recall query="ecommerce"
Enter fullscreen mode Exit fullscreen mode

Memory Patterns for Real Workflows

Pattern 1: Decision Logging

Store technical decisions when you make them, not after the context is lost.

remember({
  content: "Chose PostgreSQL over MySQL for: (1) JSONB support for flexible product attributes, (2) better full-text search, (3) UUID native support",
  tags: ["ecommerce-api", "database", "decision"],
  agent: "Backend Architect"
})
Enter fullscreen mode Exit fullscreen mode

Later, retrieve the rationale:

recall(query="PostgreSQL MySQL decision")
Enter fullscreen mode Exit fullscreen mode

This is useful when a teammate asks why a database, framework, schema, or deployment choice was made.

Pattern 2: Agent Handoffs

When switching from one agent role to another, store a handoff note containing completed work, pending work, and known issues.

remember({
  content: "Backend complete. Endpoints: POST /auth/login, POST /auth/register, GET /products, POST /orders. Auth: JWT 15min access + 7 day refresh. Pending: rate limiting, email verification. Frontend needs: login form, product list, cart, checkout.",
  tags: ["ecommerce-api", "handoff", "backend-complete"],
  agent: "Backend Architect"
})
Enter fullscreen mode Exit fullscreen mode

The frontend agent starts by retrieving the handoff:

recall(query="handoff", agent="Backend Architect")
Enter fullscreen mode Exit fullscreen mode

Pattern 3: Session Checkpoints

At the end of each session, write one checkpoint that includes completed work, next work, and blockers.

remember({
  content: "Session complete. Done: users table, auth endpoints, product schema. Next session: order system, payment webhook. Blockers: waiting for Stripe API keys.",
  tags: ["ecommerce-api", "checkpoint", "session-1"],
  agent: "Backend Architect"
})
Enter fullscreen mode Exit fullscreen mode

Resume with:

recall(query="checkpoint session-1")
Enter fullscreen mode Exit fullscreen mode

Pattern 4: Bug Tracking

Store bugs with enough detail for another session or agent to reproduce and fix them.

remember({
  content: "BUG: Refresh token not expiring after logout. Token stored in memory, not persisted. Fix: move to Redis with TTL.",
  tags: ["ecommerce-api", "bug", "auth"],
  agent: "Code Reviewer"
})
Enter fullscreen mode Exit fullscreen mode

Find known bugs later:

search(tags=["bug", "ecommerce-api"])
Enter fullscreen mode Exit fullscreen mode

Troubleshooting

Memory does not persist

Check the memory file:

ls -la ~/.mcp-memory/memories.json
Enter fullscreen mode Exit fullscreen mode

Then verify:

  • The MCP server is running before you start Claude Code or Cursor.
  • Your MCP config points to the correct absolute path for memory-server.js.
  • The memory file is readable and writable.
chmod 644 ~/.mcp-memory/memories.json
Enter fullscreen mode Exit fullscreen mode

recall returns too many results

Make retrieval more specific:

  • Add project-specific tags such as ecommerce-api.
  • Filter by agent name.
  • Use more specific query phrases.
  • Use search when you know the exact tags.

For example:

recall(query="refresh token", agent="Backend Architect")
Enter fullscreen mode Exit fullscreen mode

recall returns no results

Check whether memories were written:

cat ~/.mcp-memory/memories.json
Enter fullscreen mode Exit fullscreen mode

Then verify:

  • Your query matches text or tags in stored memories.
  • The agent filter matches the agent name used in remember.
  • You are using broader query terms when needed.
  • The MCP server is connected to the same memory file.

The memory file is too large

The JSON implementation grows indefinitely. For larger projects:

  • Archive old memories periodically.
  • Use rollback to remove no-longer-needed entries.
  • Add expiration dates to your memory schema.
  • Split memory files by project or date.
  • Move to a database backend such as SQLite.

The server fails to start

Check your Node.js version:

node --version
Enter fullscreen mode Exit fullscreen mode

Use Node.js 18 or later.

Install dependencies if needed:

npm install @modelcontextprotocol/sdk zod
Enter fullscreen mode Exit fullscreen mode

Run the server directly to view errors:

node memory-server.js
Enter fullscreen mode Exit fullscreen mode

Multiple agents overwrite or mix memories

Always include an agent value and project tags when storing entries:

remember({
  content: "Implemented login endpoint",
  tags: ["project-x", "backend", "auth"],
  agent: "Backend Architect"
})
Enter fullscreen mode Exit fullscreen mode

When retrieving, scope the result:

recall(query="auth", agent="Backend Architect")
Enter fullscreen mode Exit fullscreen mode

For stronger separation, use separate memory files per project.

What You Built

Component Purpose
MCP Memory Server Stores and retrieves information across sessions
remember tool Logs decisions, deliverables, and handoffs
recall tool Finds context from previous sessions
search tool Queries memories by tags
rollback tool Restores a previous state when needed
Memory patterns Supports decision logging, handoffs, checkpoints, and bug tracking

Memory Server Security Considerations

Avoid storing secrets in plaintext

Do not store API keys, passwords, or other sensitive values in a plain JSON memory file. If your memory server must store sensitive data, add encryption.

import crypto from "crypto";

const ENCRYPTION_KEY = process.env.MEMORY_ENCRYPTION_KEY;
const ALGORITHM = "aes-256-gcm";

function encrypt(text) {
  const iv = crypto.randomBytes(16);

  const cipher = crypto.createCipheriv(
    ALGORITHM,
    Buffer.from(ENCRYPTION_KEY),
    iv
  );

  const encrypted = cipher.update(text, "utf8", "hex");

  return {
    encryptedData: encrypted + cipher.final("hex"),
    iv: iv.toString("hex"),
    authTag: cipher.getAuthTag().toString("hex")
  };
}

function decrypt(encrypted) {
  const decipher = crypto.createDecipheriv(
    ALGORITHM,
    Buffer.from(ENCRYPTION_KEY),
    Buffer.from(encrypted.iv, "hex")
  );

  decipher.setAuthTag(
    Buffer.from(encrypted.authTag, "hex")
  );

  return (
    decipher.update(encrypted.encryptedData, "hex", "utf8") +
    decipher.final("utf8")
  );
}
Enter fullscreen mode Exit fullscreen mode

Add access control for team memory

For a shared team memory server:

  • Require an API key for memory tool calls.
  • Use user-specific or project-specific memory namespaces.
  • Log memory operations for audit trails.
  • Rate limit requests per user.

Next Steps

Extend the memory server:

  • Add semantic search with embeddings.
  • Add memory expiration and auto-archiving after 30 days.
  • Add summarization to condense long sessions.

Build shared team memory:

  • Run a central memory server for your team.
  • Tag memories by project and developer.
  • Create onboarding prompts that retrieve relevant project context.

Integrate your workflow tools:

  • Auto-log Git commits as memories.
  • Sync project updates from Jira or Linear.
  • Export important decisions to documentation.

FAQ

What is MCP memory?

MCP memory is a protocol implementation that lets AI agents store and retrieve information across sessions. It acts like a shared notebook that persists context beyond a single conversation.

How do I set up persistent memory for Claude Code?

Install or run an MCP memory server, add it to ~/.claude/settings.json, and restart Claude Code. The remember, recall, search, and rollback tools should then become available.

Which AI agents support MCP memory?

Any agent running in an MCP-compatible client, including Claude Code, Cursor, and Windsurf, can use MCP memory tools. You do not need to modify agent files; configure the server and add memory instructions to your prompts.

What are the best memory patterns for agent handoffs?

Use remember with tags such as handoff and your project name. Store completed work, pending items, technical decisions, and known issues. The receiving agent can retrieve the note with recall(query="handoff").

How much memory can MCP servers store?

It depends on the implementation. This reference server uses a JSON file that grows indefinitely. Production implementations should add expiration policies, auto-archiving, or a database backend for larger workloads.

Can teams share a central memory server?

Yes. Run the memory server on a shared machine or cloud instance, configure team clients to connect to it, and use consistent project and developer tags for retrieval.

What if memory recall returns too many results?

Use more specific tags when writing memories, filter by agent name when recalling, and query with exact phrases. For larger memory stores, add semantic search with embeddings.

Top comments (0)