DEV Community

Cover image for I Built a Self-Updating Memory System for Claude Using a Custom MCP Server
Joe Provence
Joe Provence

Posted on

I Built a Self-Updating Memory System for Claude Using a Custom MCP Server

I've been running a custom MCP server connected to Claude.ai for several months as part of a proprietary SEO intelligence platform. The setup works well for structured data queries — rankings, crawl issues, keyword gaps — but session memory was the weak point.

I had a flat markdown file that stored project context: open tasks, doctrine, session history. It loaded at the start of every session via a get_context tool. The problem? Updating it required manually editing the file on the server after every session. It kept drifting.

The fix was simpler than I expected.
I added a single append-only tool called update_context to my FastMCP server. It takes one argument — a plain text summary of what happened in the session. The tool auto-injects the date and appends a dated entry to the Session History section of the context file.

python

from datetime import datetime
from pathlib import Path

@mcp.tool()
def update_context(entry: str) -> str:
    """
    Appends a dated session history entry to the context file.
    Date is auto-injected — pass only the summary text.
    Format: "[what was done]. Next: [what to monitor or do next]."
    """
    context_path = Path.home() / "project-context.md"
    today = datetime.now().strftime("%Y-%m-%d")
    formatted = f"\n**{today}:** {entry}"

    content = context_path.read_text()
    marker = "## Session History"

    if marker not in content:
        return "Error: Session History section not found."

    insert_point = content.rfind("\n**2025-")
    if insert_point == -1:
        insert_point = content.find(marker) + len(marker)
    else:
        end_of_line = content.find("\n", insert_point + 1)
        insert_point = end_of_line

    new_content = content[:insert_point] + formatted + content[insert_point:]
    context_path.write_text(new_content)

    return f"Context updated: {formatted.strip()}"
Enter fullscreen mode Exit fullscreen mode

Now at the end of every Claude session I just say "log this session" and Claude calls the tool directly. No copy-pasting, no opening files, no forgetting.

The full memory stack looks like this:

  • get_context — loads the flat markdown file at session start (project history, open TODOs, doctrine)
  • update_context — appends session summary at session end
  • DuckDB — structured queryable data (rankings, crawl data, keyword gaps)
  • Claude.ai native memory — personal preferences and recurring facts

Three complementary layers, no RAG, no vector database, no additional infrastructure.

Why not RAG?
I evaluated it. For a single-project setup with well-curated context, RAG is overkill. The flat file loads instantly, costs nothing, and you control exactly what the model knows. RAG earns its place when you have hundreds of unstructured documents you need to search semantically. I'm not there yet.
The flat file + append tool beats RAG for the majority of single-project use cases.

The whole append tool is about 30 lines of Python. If you're already running a FastMCP server and maintaining a context file manually, this is a straightforward upgrade worth doing.
Happy to answer questions in the comments.

Top comments (0)