---
title: Give your agent persistent memory with MCP in five minutes
slug: agent-memory-mcp-five-minutes
date: 2026-10-11
author: Semurg
section: how-to
tags: [mcp, agents, memory, how-to]
summary: Install a node on your own machine, prove the seven memory tools over plain HTTP, then point your agent or framework at the same memory over MCP.
status: published
covers: [route:GET /v1/memory/status, route:POST /v1/memory/remember, route:POST /v1/memory/recall, route:POST /v1/memory/context, route:POST /v1/mcp, mcp:memory_status, mcp:memory_document, mcp:memory_remember, mcp:memory_recall, mcp:memory_relate, mcp:memory_forget, mcp:memory_context]
constants: [24.04]
---
Agents forget everything when a session ends. This guide gives yours a memory that outlives the session: a Semurg node on your own
machine, seven memory tools over MCP, and the same calls over plain HTTP. There is no account and nothing is uploaded; the memory lives on
your disk. By the end, an agent in a new session finds a fact you stored over plain HTTP.

> Diagram write-here-read-there: Your code stores that Dana works at Acme over plain HTTP; the fact lands once in one scope on your node; an agent in a new session recalls the same fact from that scope over MCP.

## What you will have

- A node on your own machine, answering on `http://localhost:4000`.
- Seven memory tools: `memory_status`, `memory_remember`, `memory_recall`, `memory_relate`, `memory_forget`, `memory_context` and `memory_document`, which stores a whole document, PDF, Word, HTML or text, as passages in one call.
- Scopes. A scope is a named, separate memory. Agents that use the same scope share what they learn; a different scope stays private.

> Diagram two-doors: An agent over MCP and your own code over HTTP reach the same memory door, which reads and writes only the scope the call names.

## Step one: install a node

Ubuntu 24.04 on x86_64, as root:

```bash
curl -fsSL https://one.semurg.io/install | sudo bash
```

The installer checks the machine first and stops with a plain reason if something is missing; the [install page](/install) lists what
it needs and what it changes. To read the script before you run it: `curl -fsSL https://one.semurg.io/install | less`. In a container
instead? The install page has the published image and how to verify it.

The installer ends with a SUCCESS banner that prints your API token. Keep it. It is also stored on the node, so you can read it back at
any time with `sudo grep '^SEMURG_API_TOKEN=' /etc/semurg/semurg.env`.

## Step two: check the memory over plain HTTP

Each block starts by putting your token in the shell; replace `<your token>` with yours, or, if you saved it in
`~/.config/semurg/token` as the [Claude Code guide](/blog/claude-code-persistent-memory) shows, run
`export SEMURG_API_TOKEN="$(cat ~/.config/semurg/token)"` once and skip that line. First, ask the node whether memory is on:

```bash runnable kind=shell-node needs=token
export SEMURG_API_TOKEN=<your token>
curl -sS http://localhost:4000/v1/memory/status \
  -H "Authorization: Bearer $SEMURG_API_TOKEN"
```

```expect
expect: a JSON answer with "ok": true, saying whether memory is on and which scopes hold facts
assert: json:.ok==true
```

Now store two entities and the relationship between them, in a scope of their own. This call lists both ends of the relationship, so
it stands on its own; a relationship may also name an entity an earlier call stored.

```bash runnable kind=shell-node needs=token
export SEMURG_API_TOKEN=<your token>
curl -sS http://localhost:4000/v1/memory/remember \
  -H "Authorization: Bearer $SEMURG_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"scope": "five-minutes",
       "source": "five-minute guide",
       "facts": {
         "entities": [{"name": "Acme", "type": "Company"},
                      {"name": "Dana", "type": "Person"}],
         "relations": [{"source": "Dana", "target": "Acme",
                        "type": "WORKS_AT"}]}}'
```

```expect
expect: a JSON answer with "ok": true
assert: json:.ok==true
```

Storing the same facts again changes nothing, so it is safe to run twice. Now recall one of the entities by name. The answer is each
fact connected to it, with its relation and the ids of the entities at both ends:

```bash runnable kind=shell-node needs=token
export SEMURG_API_TOKEN=<your token>
curl -sS http://localhost:4000/v1/memory/recall \
  -H "Authorization: Bearer $SEMURG_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"scope":"five-minutes","subject":"Dana"}'
```

```expect
expect: "state": "answered", with the WORKS_AT fact that connects Dana to Acme
assert: json:.ok==true and .state=="answered" and ([..|strings|ascii_downcase|select(contains("acme"))]|length>0)
```

An agent usually has a question rather than a name. `memory_context` takes the question, starts from every stored name it mentions,
walks the relationships around them and returns the ranked facts:

```bash runnable kind=shell-node needs=token
export SEMURG_API_TOKEN=<your token>
curl -sS http://localhost:4000/v1/memory/context \
  -H "Authorization: Bearer $SEMURG_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"scope":"five-minutes","query":"Where does Dana work?"}'
```

```expect
expect: "state": "answered", with the fact that Dana works at Acme
assert: json:.state=="answered" and ([..|strings|ascii_downcase|select(contains("acme"))]|length>0)
```

When nothing matches, the node says so instead of guessing. A name it has never stored answers with an empty list and a note saying
what was tried:

```bash runnable kind=shell-node needs=token
export SEMURG_API_TOKEN=<your token>
curl -sS http://localhost:4000/v1/memory/recall \
  -H "Authorization: Bearer $SEMURG_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"scope":"five-minutes","subject":"Nobody"}'
```

```expect
expect: "state": "nothing_stored" and no items: an empty answer that says it is empty
assert: json:.state=="nothing_stored"
```

Read `state` before you use the items. That one habit keeps an agent from treating "nothing found" as a fact about the world.

## Step three: connect your agent over MCP

Point any MCP client at `http://127.0.0.1:4000/v1/mcp`. It speaks streamable HTTP and takes your token as a bearer token. The optional
`X-Semurg-Scope` header sets the default scope for the connection, so the agent's calls land in the scope you choose.

For Claude Code, one command connects every Claude Code session you start, in any project. The settings file holds only a reference
to the environment variable, never the token, so set `SEMURG_API_TOKEN` in the shell that starts Claude Code (your shell profile, for
example):

```bash
export SEMURG_API_TOKEN=<your token>
claude mcp add-json --scope user semurg '{
  "type": "http",
  "url": "http://127.0.0.1:4000/v1/mcp",
  "headers": {
    "Authorization": "Bearer ${SEMURG_API_TOKEN}",
    "X-Semurg-Scope": "five-minutes"
  }
}'
claude mcp list
```

`claude mcp list` should show the node as connected. In a new session, ask what the memory holds about Dana: the agent can call
`memory_recall` in the scope you set and find the fact you stored over HTTP in step two. That is the point of the exercise. What one
client writes, any other client on the same node and scope reads back.

## Step four: the same memory from your framework

Agent frameworks reach the same seven tools as ordinary code on your side. The [agents page](/agents) carries a complete program for
each framework, with the release it last ran on in our tests; each one stores two facts and reads them back.

LangChain and LangGraph, LlamaIndex, Google ADK, Mastra, the Vercel AI SDK and LangChain for Elixir connect through their own MCP
clients, and in our tests each of those clients reconnected by itself when we restarted the node underneath it. That matters,
because an update restarts the node. Jido calls the same memory over the HTTP routes from step two instead: every call is its own
request, so there is no session to lose.

Reconnecting is not the same as giving up in time. If the node is up but stalled, a plain client waits as long as its own timeout
allows, and in our tests some framework clients waited more than three minutes before they returned. Set the per-call timeout
your framework offers, so an agent that cannot reach its memory says so quickly instead of hanging.

Four other clients, CrewAI, the Microsoft Agent Framework, the OpenAI Agents SDK and Pydantic AI, needed a new client after a restart.

## What to expect on today's release

Said plainly, so you can plan around it:

- Recall and context find stored names by their spelling: exact, letter case, a similar spelling, one whole word of a longer name, or the start of a word. They also find by meaning once the node's embedding model is installed: a node that agrees to downloads installs it by itself when the signed catalogue lists it. Until then a remember reply says `meaning: not_armed` and only names match; after, it says `meaning: queued`.
- Raw text sent as `content` is stored at once and linked to the entities already stored (the reply says `extraction: known_names_only`). Where an extraction model is installed and serving, the node also reads the text afterwards and adds the entities it finds, and the reply's `model_extraction` says whether that is pending, done, or why it is idle. Sending the facts your agent has extracted, as in step two, is still the fast path.
- A relationship may name an entity stored by an earlier call. If it names one that is neither in this call nor stored, the call is refused by name and nothing is stored, so a misspelt name never slips through.
- Each recalled item carries a citation: the source it came from, when it was stored and the exact span it quotes.
- Forgetting deletes one thing per call: an entity, one fact, a document, everything under a source label, or the whole scope. Forgetting an entity deletes it, its facts and the passages linked to it; a passage that contains the name without being linked to it (for example text stored before the entity was) stays, and the reply lists it in `passages_retained`, so forget that document too if it must go. What is deleted is unreachable at once, and its bytes are wiped from the disk later, with space reclaim, which is not released yet. Forgetting the same entity again answers `200` with `already_forgotten: true`; a name that was never stored answers `404` with a close spelling suggested.

Each of these is being worked on, and this post will be updated, with the release named, as each one changes.

## Where to go next

- The [agents page](/agents): configuration for more agents (Hermes Agent, OpenClaw, Cursor, the OpenAI Codex CLI, Gemini CLI) and every framework program.
- The [develop page](/develop): the HTTP routes in full, the limits each call refuses by name, and a rules file that helps a coding agent build an app on the node.
- The [install page](/install): data disks, containers, updating and removing a node.
