Skip to main content
AI & Full-Stack·14 min read

Build Persistent Memory for AI Agents with TypeScript, SQLite & MCP (2026 Guide)

Stop losing context between AI agent sessions. Practical production guide to implementing persistent memory for coding agents using TypeScript, better-sqlite3, FTS5 search, and the Model Context Protocol — inspired by the 100k+ star claude-mem pattern.

By Mussawar Hayat

Why AI Agents Forget Everything (and How to Fix It)

In late 2026 the hottest conversation on X around AI coding agents is no longer raw model power. It is memory. Tools like claude-mem (over 105,000 GitHub stars) exploded because agents still start every session as a blank slate. This guide shows you how to build the same capability yourself in TypeScript for Next.js and custom agents.

What You Will Build

  • A production-ready SQLite memory store with FTS5 full-text search
  • An MCP server that exposes memory tools any agent can call
  • Progressive retrieval so you inject only relevant context
  • Privacy controls, compression patterns, and security practices
  • Integration examples for Claude Code, Cursor, and custom Next.js agents

1. The Problem: Context Resets Kill Productivity

Every coding agent session begins empty. Decisions made yesterday, bug fixes, file ownership, and architecture choices disappear. Developers re-explain the same context repeatedly. Token costs rise and quality drops.

claude-mem solved this by hooking into session lifecycle events, compressing tool observations with a model, storing them in SQLite + vector search, and injecting a compact index on the next session start. The same pattern works for any agent that supports MCP or custom tools.

Official sources: claude-mem docs and the MCP specification.

2. Architecture Overview

A minimal but production-capable memory system has three pieces:

  • Storage — SQLite with WAL mode and FTS5 for fast local search. No external services required.
  • MCP Server — Tools for create, search, get, and delete memories. Agents call these automatically.
  • Progressive Disclosure — Return a short index first; full observation only when the agent requests the ID.

For Next.js apps you can also write memories from Server Actions and read them for agent orchestration. The MCP server remains the portable interface for external agents.

3. Step-by-Step Implementation

Install dependencies

npm install @modelcontextprotocol/sdk better-sqlite3 zod
npm install -D @types/better-sqlite3 typescript

Database schema (src/memory/db.ts)

import Database from 'better-sqlite3'
import { drizzle } from 'drizzle-orm/better-sqlite3'

const sqlite = new Database(process.env.MEMORY_DB_PATH ?? './agent-memory.db')
sqlite.pragma(`journal_mode = WAL`)
sqlite.pragma(`foreign_keys = ON`)

sqlite.exec(`
  CREATE TABLE IF NOT EXISTS memories (
    id TEXT PRIMARY KEY,
    title TEXT NOT NULL,
    content TEXT NOT NULL,
    type TEXT DEFAULT 'observation',
    project TEXT,
    created_at TEXT DEFAULT (datetime('now')),
    updated_at TEXT DEFAULT (datetime('now'))
  );
  CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
    title, content, content='memories', content_rowid='rowid'
  );
`)

export const db = sqlite

Core memory tools (MCP)

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import { z } from 'zod'
import { db } from './db.js'
import { randomUUID } from 'crypto'

const server = new McpServer({ name: 'agent-memory', version: '1.0.0' })

server.tool(
  'memory_create',
  'Store a new observation or decision for later retrieval',
  {
    title: z.string().describe('Short title'),
    content: z.string().describe('Full observation or decision text'),
    type: z.string().optional().default('observation'),
    project: z.string().optional()
  },
  async ({ title, content, type, project }) => {
    const id = randomUUID()
    db.prepare(
      `INSERT INTO memories (id, title, content, type, project) VALUES (?, ?, ?, ?, ?)`
    ).run(id, title, content, type, project ?? null)
    return { content: [{ type: 'text', text: `Stored memory ${id}` }] }
  }
)

server.tool(
  'memory_search',
  'Search past memories by keyword. Returns compact index with IDs.',
  {
    query: z.string().describe('Search terms'),
    limit: z.number().optional().default(5)
  },
  async ({ query, limit }) => {
    const rows = db.prepare(
      `SELECT m.id, m.title, snippet(memories_fts, 1, '', '', '...', 20) as snippet
       FROM memories_fts f JOIN memories m ON f.rowid = m.rowid
       WHERE memories_fts MATCH ? ORDER BY rank LIMIT ?`
    ).all(query, limit)
    return { content: [{ type: 'text', text: JSON.stringify(rows, null, 2) }] }
  }
)

const transport = new StdioServerTransport()
await server.connect(transport)

Run the server with npx tsx src/memory/server.ts and add it to your agent's MCP config.

4. Production Best Practices

  • Always use WAL mode and enable foreign keys.
  • Return short indexes first (title + snippet + id). Full content only on explicit get-by-id.
  • Tag memories with project and type so agents can filter.
  • Compress long tool outputs before storage (send a summary, keep raw optional).
  • Version the schema and run migrations.

Security considerations

Never store secrets, API keys, or PII. Use <private> markers or a redaction pass before insert. Restrict the MCP server to local stdio when possible. If you expose HTTP, require authentication and scope tools by user or project. Treat memory content as untrusted input when injecting back into prompts.

Performance notes

FTS5 is fast enough for tens of thousands of observations on a single machine. For multi-user Next.js apps prefer PostgreSQL with full-text search or a dedicated vector store, and keep the MCP interface identical so agents do not care about the backend.

5. Common Mistakes

  • Injecting full raw tool logs instead of compressed summaries — token waste.
  • No progressive disclosure — dumping every memory into context.
  • Storing secrets or large binaries.
  • Forgetting to rebuild the FTS index after bulk inserts.
  • Running the MCP server without WAL mode under concurrent agent use.
  • Not filtering by project so agents see unrelated memories.

6. Real Use Cases

  • Coding agents — Remember architecture decisions, file ownership, and previous bug fixes across days.
  • Next.js product agents — Persist user preferences and past support resolutions for a customer-facing assistant.
  • Multi-agent crews — Shared memory so research, coding, and review agents stay aligned.
  • Onboarding — New agent sessions automatically load the last ten relevant observations for a project.

7. FAQ

Can I just use claude-mem instead?

Yes for Claude Code and supported hosts. This guide is for teams that want a portable, self-hosted memory layer that works with any MCP client or custom Next.js agent, or that need to store memories inside their own application database.

Does this work with Cursor and Codex?

Any client that supports MCP stdio or Streamable HTTP can call the memory tools. Configure the server in the client's MCP settings.

How do I add semantic / vector search?

Keep FTS5 for keyword recall and add a separate embedding column or Chroma/Postgres pgvector store. Query both and merge results. claude-mem does exactly this.

Is SQLite safe for production?

For a single-user or single-project coding agent, yes. For multi-tenant SaaS agents, move the same schema to PostgreSQL and keep the MCP tool interface unchanged.

8. Summary

Persistent memory is the practical layer that turns short-lived coding agents into reliable long-running collaborators. The pattern proven by claude-mem — capture, compress, store, progressive retrieve — is straightforward to implement in TypeScript with SQLite and the Model Context Protocol.

Key Takeaway

Give agents a durable memory store they can search on demand. Return a compact index first, full detail only when requested, and never store secrets. The same MCP tools work across Claude Code, Cursor, custom Next.js agents, and multi-agent systems.


Need help adding reliable memory and guardrails to your AI agents?

I design production TypeScript and Next.js agent systems, MCP servers, and secure data layers. Get in touch or explore full-stack and AI development services.

Related reading: Agent Skills for TypeScript & Next.js and Building Production MCP Servers in TypeScript.