Persistent memory for Mastra agents, on your own machine
25 October 2026 · Semurg · 5 min read · Markdown
Last checked on r11.132.20261027.a274a28 (27 Oct 2026): every command below ran on a fresh node of that release. The current release is r11.135.20261028.2ff2348 (28 Oct 2026), and the commands have not been re-checked on it yet (how)
mastratypescriptmcpagentsmemoryhow-to
Tested end to end with
@mastra/mcp 2.5.0and@mastra/core 1.79.0. The release it ran on is named in the checked line at the top of this page.
A Mastra agent forgets what it learned when its session ends. This guide gives it a memory that outlives the session: a Semurg node on your own machine, reached through Mastra's own MCPClient, where every fact stays on your disk and comes back cited to where it was said. Any other agent or framework that uses the same node and the same scope reads the same memory.
What you need
- An Intel or AMD x86_64 machine with Ubuntu 24.04, on Linux directly or on Windows through WSL2. One node is free, with no licence key.
- Node.js 22 and npm. We test in the official
node:22-slimcontainer.
Step one: install a node
On Ubuntu 24.04 (x86_64), with sudo:
curl -fsSL https://one.semurg.io/install | sudo bashThe install page lists what the installer needs and what it changes. The installer ends by printing your API token, and the node keeps a copy. Put it in a file only you can read, and have every new terminal load it:
mkdir -p ~/.config/semurg
(umask 077; sudo grep '^SEMURG_API_TOKEN=' /etc/semurg/semurg.env | cut -d= -f2- > ~/.config/semurg/token)
echo 'export SEMURG_API_TOKEN="$(cat ~/.config/semurg/token)"' >> ~/.bashrc
export SEMURG_API_TOKEN="$(cat ~/.config/semurg/token)"Step two: install Mastra's MCP client
Make a folder for the program. It is an ES module, so the folder's package.json says so, as in the setup we test; tsx runs the TypeScript file directly.
mkdir ~/semurg-mastra && cd ~/semurg-mastra
npm init -y && npm pkg set type=module
npm install @mastra/mcp @mastra/core tsx zodStep three: connect
Save the program below as snippet.ts in that folder. It is the program our tests run as written, shown here from the agents page rather than copied, so what you read is always the file that ran.
// Run: npx tsx snippet.ts (SEMURG_API_TOKEN must be set in the environment)
import { MCPClient } from "@mastra/mcp";
const url = process.env.SEMURG_MCP_URL ?? "http://127.0.0.1:4000/v1/mcp";
const headers = {
Authorization: `Bearer ${process.env.SEMURG_API_TOKEN}`,
"X-Semurg-Scope": process.env.SEMURG_MCP_SCOPE ?? "default",
};
const facts = {
entities: [{ name: "Ada", type: "person" }, { name: "Acme", type: "organisation" }],
relations: [{ source: "Ada", target: "Acme", type: "WORKS_AT" }],
};
async function main() {
const mcp = new MCPClient({ servers: { semurg: { url: new URL(url), requestInit: { headers } } } });
try {
const tools: Record<string, any> = await mcp.listTools(); // Mastra prefixes each tool with the server name
await tools["semurg_memory_remember"].execute({ facts }, {});
console.log(JSON.stringify(await tools["semurg_memory_recall"].execute({ subject: "Ada" }, {})));
} finally {
await mcp.disconnect();
}
}
main().catch((e) => {
console.error(e);
process.exit(1);
});Ran: this exact file, end to end against a fresh node on release r11.132.20261027.a274a28 in node:22-slim, with @mastra/mcp 2.5.0, @mastra/core 1.79.0. Install: npm install @mastra/mcp @mastra/core tsx zod. Its run record and every other framework: /agents.
It reads your token from SEMURG_API_TOKEN, the node's address from SEMURG_MCP_URL (without it, http://127.0.0.1:4000/v1/mcp) and the scope from SEMURG_MCP_SCOPE (without it, the scope named default). It builds an MCPClient with one server, semurg, whose requests carry the token and the X-Semurg-Scope header, stores two facts through the remember tool (Ada works at Acme), prints what the recall tool returns for Ada as JSON and disconnects.
Step four: run it
Give the program a scope of its own, so it never writes into a memory you use for real work:
export SEMURG_MCP_SCOPE=try-mastra
npx tsx snippet.tsIt prints the recall result as JSON, which carries the node's answer for Ada: state set to answered and the WORKS_AT fact that links Ada to Acme, each item cited. Storing the same facts again changes nothing, so it is safe to run twice.
Now read the same fact from outside Mastra, over plain HTTP. What one client writes, any other client on the same node and scope reads:
curl -sS http://127.0.0.1:4000/v1/memory/recall \
-H "Authorization: Bearer $SEMURG_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"scope":"try-mastra","subject":"Ada"}'The memory tools in Mastra
Mastra prefixes each tool with the name of its server, so mcp.listTools() returns semurg_memory_remember, semurg_memory_recall and so on; the program calls them with execute. The same tools are what you give a Mastra agent, and the model then chooses (our test calls the tools directly, without a model).
memory_status: reports whether shared memory is on for this node and which of your scopes hold facts.memory_remember: stores entities, the relationships between them and the passages they came from, or plain text, with an optional source label and date.memory_recall: recalls what is stored about one named entity, its own facts first, each item cited.memory_relate: adds one typed relationship between two entities in a scope, creating an end that is not stored yet.memory_context: answers a question in plain words with cited items and a verdict on whether the store covers it.memory_document: takes a whole document (PDF with a text layer, Word, HTML or plain text) and stores it as cited passages in a scope.memory_forget: deletes one thing per call: an entity and everything that names it, one fact, one document, every document under a source label, or the whole scope. It is unreachable at once and not yet wiped from the disk.memory_export: reads everything a scope holds, one page per call: send each reply'snextback ascursoruntil it is empty. The records are the entities, the facts with their dated sources and whether each is current or ended, and each stored document once; a forgotten passage is never exported.memory_import: stores the recordsmemory_exportproduced into any scope, through the same writesmemory_remembermakes. A page sent twice stores nothing twice, and the reply names every record it skipped.
Every answer says its state: answered, nothing_stored (nothing stored matched, so the agent should say it does not know) or could_not_judge.
Troubleshooting
TypeError: Cannot read properties of undefined (reading 'execute'): in our tests Mastra's client listed no tools at all when the node refused the token, so the tool was undefined. Check thatSEMURG_API_TOKENis set in this shell and is your node's token.- A tool that is not found under
memory_recall: Mastra names itsemurg_memory_recall, after the server keysemurg. Rename the key and the prefix changes with it. - An error about
importstatements: run the program from the folder made in step two, whosepackage.jsonsets"type": "module". - After the node restarts (an update restarts it), the first call can fail; in our October 2026 restart test, run on an earlier release, Mastra's client then reconnected by itself. For a node that is up but stalled, set the per-call timeout your framework offers.
Use 127.0.0.1 rather than localhost: on Ubuntu localhost can resolve to the IPv6 address first, and the node listens on IPv4 loopback only. To reach a node on another machine, set SEMURG_MCP_URL, or forward its port with ssh -N -L 4000:127.0.0.1:4000 you@your-node.
Next
- The agents page: every framework program, with its versions and the release it last ran on in our tests.
- Give your agent persistent memory with MCP in five minutes: the memory calls over plain HTTP too.
- Connecting to Semurg over MCP: the endpoint, the protocol versions it speaks and how a refusal comes back.
- If a step does not work, tell us on the contact page, with the reply you got.
This post was written with AI assistance. Every result in it is checked against a published release or record before the post goes out.
How these commands were checked
Our test farm ran each program shown from /agents, the very file our test farm ran end to end, against a fresh node of the published release r11.132.20261027.a274a28. A post is published only when every one of them passed there.