Module 3 video goes live soon. The full lesson is below, and the checkpoint is ready to download.

Give it a memory

Lesson 20 of 30, Module 3: Make It Trustworthy. It refuses to leave the folder, asks before it overwrites, and remembers your answers.

Table of Contents
Downloads

Lesson 20 of 30, Module 3: Make It Trustworthy. It refuses to leave the folder, asks before it overwrites, and remembers your answers.

What you can do after this lesson

Put state in a file the server owns, next to the server file and not in the chat, and a rule you give once survives every new chat. Anchor the path to the server file, because a host can start the server from a folder you cannot write to.

The problem we inherited

A server that can write can write over the wrong document, and there is no undo you can reach from a chat.

The 5 steps this lesson runs

  1. Write the plain Python function
  2. Put the @mcp line above it
  3. Write the one-line docstring
  4. Restart Claude Desktop
  5. Ask Claude to use it

Code in this lesson

Every block the lesson shows on screen, in the order it appears.

1. Write the plain Python function
2. Put the @mcp line above it
3. Write the one-line docstring
4. Restart Claude Desktop
5. Ask Claude to use it
HERE = os.path.dirname(os.path.abspath(__file__))
MEMORY = os.environ.get("SCRIBE_MEMORY",
                        os.path.join(HERE, "memory.json"))
def _notes() -> list:
    if not os.path.exists(MEMORY):
        return []
    with open(MEMORY) as f:
        return json.load(f)


def _save(notes: list) -> None:
    with open(MEMORY, "w") as f:
        json.dump(notes, f, indent=2)


def _trusted(name: str) -> bool:
    return any(n["note"] == f"trust {name}" for n in _notes())
def remember(note: str) -> str:
    notes = _notes()
    notes.append({"on": str(date.today()), "note": note})
    _save(notes)
    return f"Remembered. Scribe now holds {len(notes)} note(s)."


def recall() -> str:
    notes = _notes()
    if not notes:
        return "Scribe remembers nothing yet."
    return "\n".join(f"{n['on']}: {n['note']}" for n in notes)
@mcp.tool()
def remember(note: str) -> str:
    """Keep one note for every future chat. A rule, or a choice."""
    notes = _notes()
    notes.append({"on": str(date.today()), "note": note})
    _save(notes)
    return f"Remembered. Scribe now holds {len(notes)} note(s)."


@mcp.tool()
def recall() -> str:
    """Every note Scribe remembers, oldest first."""
    notes = _notes()
    if not notes:
        return "Scribe remembers nothing yet."
    return "\n".join(f"{n['on']}: {n['note']}" for n in notes)
def ask_first(name: str, find: str, replace: str,
              said_yes: bool, ctx: Context):
    """Ask before a write, unless you told Scribe to trust it."""
    if not find:
        raise ToolError("Tell me what to find. An empty find"
                        " would change every spot in the draft.")
    if _trusted(name) or said_yes:
        return Confirm(yes=True)
    question = f"Change every '{find}' to '{replace}' in {name}?"
    if _can_ask(ctx):
        return Elicit(question, Confirm)
    raise ToolError(
        f"{question} This app cannot show my question, so I"
        " stopped. Ask the person. If they say yes, call me"
        " again with said_yes set to true.")
Use scribe to remember that my openings stay under 12 words.
What does scribe remember?
Use scribe to remember exactly these words: trust draft.md

Downloads

Prefer to read?

The written version of this part of the course is on Substack: https://genaiunplugged.substack.com/p/give-your-ai-agents-memory-mcp-shared.

Lesson transcript

Scribe remembers, and the map

Today Scribe remembers what I told it last week. And the guard stops asking about the draft I trust.

Hello, and welcome to lesson 20 of the MCP Masterclass. Last lesson we found out why every chat starts at zero, and where the rules should live instead. Today we build that. And we start with our map, as always.

12 lit boxes. By the end of this lesson the memory lights up, right beside the guard.

The same five steps, run 4

Well, you know the list. Here it is, and it has not changed by a word.

1. Write the plain Python function
2. Put the @mcp line above it
3. Write the one-line docstring
4. Restart Claude Desktop
5. Ask Claude to use it

Write the plain Python function. Put the @mcp line above it. Write the one line docstring. Restart Claude Desktop. Ask Claude to use it.

Run number 4. I am not going to explain the steps this time. Watch the clock instead.

The memory is one file

Before the steps, 1 line near the top of the file, under the other settings.

HERE = os.path.dirname(os.path.abspath(__file__))
MEMORY = os.environ.get("SCRIBE_MEMORY",
                        os.path.join(HERE, "memory.json"))

The memory is a file called memory.json, and it sits next to your server. That is the whole design. No database, nothing to install, and you can open it in any text editor and read it.

The first line matters more than it looks. Here is the folder your server file lives in, and the memory goes beside it. I did not write that at first. I wrote plain memory.json, and it worked in my terminal and failed inside Claude Desktop, because Claude Desktop starts your server from a folder you cannot write to. Next to your server means next to the file, not wherever the app happened to start it.

Then 3 small helpers.

def _notes() -> list:
    if not os.path.exists(MEMORY):
        return []
    with open(MEMORY) as f:
        return json.load(f)


def _save(notes: list) -> None:
    with open(MEMORY, "w") as f:
        json.dump(notes, f, indent=2)


def _trusted(name: str) -> bool:
    return any(n["note"] == f"trust {name}" for n in _notes())

Notes reads the file, and gives back an empty list if there is no file yet. Save writes the list back. And trusted asks 1 question of the notes. Is there a note that says, trust this draft?

Two tools in 40 seconds

Okay. The 5 steps. Step 1, the plain Python function. Two of them, because a memory you cannot read back is a diary with a lock and no key.

def remember(note: str) -> str:
    notes = _notes()
    notes.append({"on": str(date.today()), "note": note})
    _save(notes)
    return f"Remembered. Scribe now holds {len(notes)} note(s)."


def recall() -> str:
    notes = _notes()
    if not notes:
        return "Scribe remembers nothing yet."
    return "\n".join(f"{n['on']}: {n['note']}" for n in notes)

Remember takes 1 sentence, stamps today's date on it, and saves it. Recall gives every note back, oldest first.

Step 2, the @mcp line. Step 3, the docstring.

@mcp.tool()
def remember(note: str) -> str:
    """Keep one note for every future chat. A rule, or a choice."""
    notes = _notes()
    notes.append({"on": str(date.today()), "note": note})
    _save(notes)
    return f"Remembered. Scribe now holds {len(notes)} note(s)."


@mcp.tool()
def recall() -> str:
    """Every note Scribe remembers, oldest first."""
    notes = _notes()
    if not notes:
        return "Scribe remembers nothing yet."
    return "\n".join(f"{n['on']}: {n['note']}" for n in notes)

And now the guard learns to use the memory. One change inside ask first, on 1 line.

def ask_first(name: str, find: str, replace: str,
              said_yes: bool, ctx: Context):
    """Ask before a write, unless you told Scribe to trust it."""
    if not find:
        raise ToolError("Tell me what to find. An empty find"
                        " would change every spot in the draft.")
    if _trusted(name) or said_yes:
        return Confirm(yes=True)
    question = f"Change every '{find}' to '{replace}' in {name}?"
    if _can_ask(ctx):
        return Elicit(question, Confirm)
    raise ToolError(
        f"{question} This app cannot show my question, so I"
        " stopped. Ask the person. If they say yes, call me"
        " again with said_yes set to true.")

If the draft is trusted, ask first hands back a yes on your behalf, and no question appears, in either kind of app. Otherwise it asks, exactly as before.

So the docstring you wrote in lesson 17 just came true.

Step 4. Restart Claude Desktop. Step 5. Ask Claude to use it.

Use scribe to remember that my openings stay under 12 words.

On screen: Claude Desktop calls remember. "Remembered. Scribe now holds 1 note(s)."

Remembered. And that took about 40 seconds. No commentary needed.

Proof it survives a new chat

Now the part that matters. Close that chat. Open a brand new one.

What does scribe remember?

On screen: a new chat. Claude calls recall. "2026-09-30: my openings stay under 12 words."

Same rule, new chat. It lives in the file now, not in the conversation.

And you can prove it without Claude at all. The second script in your checkpoint is called prove_memory.py. It tells the server 1 note, stops the server, starts it again, and asks.

On screen: prove_memory.py. "Remembered. Scribe now holds 1 note(s)." Then the restart. Then the note read back, and the file itself. 3 PASS. "RESULT: Scribe remembers."

The note survives the restart, and the last thing the script does is print the file, so you can see there is no magic in it. A date, and your words.

Trust one draft

One more thing, and it is the reason the guard got 2 new lines.

Use scribe to remember exactly these words: trust draft.md

On screen: Claude calls remember with "trust draft.md". Then an edit to draft.md runs, and no question appears.

Tell Scribe to trust a draft. Say exactly these words, because the note has to match the draft's name and nothing else. From now on, edits to that 1 draft run without the question, in every app, in every chat. Every other draft still asks.

And if you change your mind, open memory.json and delete the line. It is your file.

What it cost and the map

So what did all of that cost you? Two tools and 3 helpers, about 25 lines, and the 5 steps for the fourth time. The list has still not changed by a word.

So where does our map stand at the end of this lesson?

13 boxes lit. The guard and the memory, side by side, and the guard reads the memory.

Next lesson is different. I set you an assignment, and you build 1 tool on your own.

Bye now, and I will see you in the next lesson.

Frequently Asked Questions

The written version of this part of the course is on Substack: https://genaiunplugged.substack.com/p/give-your-ai-agents-memory-mcp-shared.

Dheeraj Sharma

Dheeraj Sharma

AI Systems Builder
Creator of the n8n Zero to Hero course (42 lessons, 31+ hours). I help solopreneurs build AI systems that grow revenue without growing workload.

Get the Scribe checkpoints and code

Every module ships a checkpoint zip with the server exactly as the module leaves it, so you can start any lesson from working code.

Open the GitHub repo