Skip to main content
Glama
H1an1

mem-universe

by H1an1
README.md
# mem-universe

A self-hosted **MCP memory server** that gives a personal multi-agent fleet
(Claude Code, ChatGPT, Codex, Cursor, and any other MCP client) **one shared,
git-backed memory**. Agents install nothing. They connect over MCP and
`search` / `read` / `write`.

> The problem it solves: *"Claude on one machine figured out how to do X, but
> Codex on another machine has no idea."* One memory, every agent, instantly.

---

## The idea

Most "give your agent memory" setups sync skill files into each tool, in each
tool's own format. That does not scale across a fleet: every new agent needs a
converter, and knowledge learned in one place is invisible everywhere else.

mem-universe flips it: keep **one** plain-text store, and let every agent read
and write it **at use time** over MCP. A skill is just markdown any agent can
follow, so there is nothing to convert. Learn something once, and the whole
fleet can recall it on the next task.

---

## Architecture

```mermaid
flowchart LR
  subgraph agents [Your agent fleet]
    A1[Claude Code]
    A2[ChatGPT]
    A3[Codex / Cursor]
    A4[Other MCP clients]
  end

  A1 -- "Bearer token" --> S
  A3 -- "Bearer token" --> S
  A4 -- "Bearer token" --> S
  A2 -- "token in URL path" --> S

  subgraph server [mem-universe server  ·  FastMCP over HTTP]
    S[auth + path validation]
    S --> T["tools: search / read / write / list / delete<br/>put_skill / get_skill / list_skills"]
    T --> IDX[("BM25 index<br/>local, rebuildable")]
    T --> ST[git working copy]
  end

  ST -- "commit, then async push" --> GH[("private store repo<br/>on GitHub")]
  ST --> L1["shared/  ·  skills, lessons, rules"]
  ST --> L2["personal/  ·  owner profile, notes"]
```

### Key design decisions

1. **MCP is the integration layer, not file-syncing.** Knowledge is stored once
   as plain text and retrieved on demand. Add one entry and the whole fleet sees
   it, with no per-agent format conversion.

2. **Git is the database.** The store is a git repo of markdown files. Every
   `write` is a commit, then an async push to GitHub. You get version history,
   recoverable deletes (delete is also a commit), human-readable data, and
   "backup" for free. Writes are serialized by a single-writer lock; reads come
   from the local clone, so they never wait on the network. Last write wins, so
   there are no merge conflicts by design.

3. **Code and data live in two separate repos.** This server (code) is one repo.
   Your memory (data) is a *separate, private* store repo that the server clones
   and writes to. That split is why this codebase can be public while your
   memory stays private. **Never put the store in this repo.**

4. **Two layers, one permission.** `shared/` is cross-agent knowledge (skills,
   lessons, rules). `personal/` is the owner's own content. Any valid token can
   read and write everything: tokens gate *who connects*, not *which layer*. For
   a single owner's fleet on a private network, scoped permissions are
   over-engineering. Anything truly secret should not go in the store at all.

5. **Retrieval, not installation.** Agents install nothing. To make an agent
   *use* the library, drop a one-line "recall first" marker into its existing
   instruction file (`CLAUDE.md`, `AGENTS.md`, ...) telling it to `search`
   before acting. Multi-file, runnable skills travel as packages
   (`put_skill` / `get_skill`).

6. **Two ways to authenticate, so Claude and ChatGPT both connect.** Clients
   that can send headers use `Authorization: Bearer <token>`. Clients that
   cannot set custom headers (such as a ChatGPT connector) put the token in the
   URL path: `https://<host>/<token>/mcp`. Same server, both worlds.

7. **Hand-rolled, bilingual BM25 search.** No external embedding service. It is
   light, works offline, and tokenizes mixed CJK + English. The index lives
   locally and is never committed (it rebuilds from the store).

---

## The tools

| Tool | What it does |
|------|--------------|
| `search` | BM25 search across the store; returns ranked `{path, score, snippet, type}` |
| `read` | Read one entry by store-relative path |
| `write` | Write an entry (also indexes it); `layer` inferred from the path |
| `list` | List entries under a layer / prefix |
| `delete` | Delete an entry (git keeps history, so it is recoverable) |
| `put_skill` | Store a multi-file skill package under `shared/skills/<name>/` |
| `get_skill` | Fetch a skill package to install locally |
| `list_skills` | List available skill packages |

## Layers

```
<store>/
├── shared/      # cross-agent knowledge: skills / tools / lessons / rules
└── personal/    # the owner's own content: profile, notes, imports
```

A `write` path starts with the layer name (`shared/...` or `personal/...`); the
`layer` argument is optional and inferred from the path.

## What happens on one write

```
write(path, content)
  -> validate path (must stay in-layer; reject ../ and cross-layer symlinks)
  -> acquire single-writer lock
  -> write file + stamp frontmatter
  -> git commit
  -> return ok
  -> (background) async push to the private store repo
  -> update the local BM25 index
```

Reads and searches always hit the local clone, so they do not block on git.

---

## Deploy

Full steps are in [`DEPLOY.md`](DEPLOY.md). The short version (Docker):

```bash
git clone https://github.com/H1an1/mem-universe && cd mem-universe/deploy
cp mem.env.example mem.env     # set MEMORY_TOKEN and MEM_STORE_REMOTE (your private store repo)
# edit Caddyfile: point your domain at this VPS
docker compose up -d --build
```

This runs the server behind Caddy (automatic TLS). Agents reach it at
`https://<your-domain>/mcp`. Generate a token with `openssl rand -hex 32`.
A Tailscale-only (no public exposure) compose file is also included.

## Connect your agents

All clients need the **URL** plus a **token**.

**Claude Code**
```bash
claude mcp add --transport http mem-universe https://<your-domain>/mcp \
  --header "Authorization: Bearer <YOUR_TOKEN>"
```

**Claude Desktop / claude.ai** (custom connector): add an HTTP MCP server with
URL `https://<your-domain>/mcp`. If the client supports a header, use
`Authorization: Bearer <YOUR_TOKEN>`; otherwise use the path-token URL below.

**ChatGPT** (custom connector): ChatGPT connectors cannot set custom headers, so
put the token in the path:
```
https://<your-domain>/<YOUR_TOKEN>/mcp
```

**Cursor / Codex / others**: add an HTTP MCP server with the same URL and an
`Authorization: Bearer <YOUR_TOKEN>` header (see your client's MCP docs).

Every agent uses the same token. To make agents actually *recall* before acting,
add a short "recall first" marker to each agent's instruction file.

---

## Security model

- One token unlocks every layer; tokens gate connection, not content. A
  read-only token variant exists for untrusted external readers.
- The server rejects path traversal (`../`) and cross-layer symlinks at the
  filesystem level, independent of auth.
- Run it on a private network (such as Tailscale) or behind TLS with a strong
  token. **Anything that truly must never be read by any agent does not belong
  in the store.**
- Secrets live only in `deploy/mem.env` (gitignored). Never commit them.

## How it was built

Built with a maker/checker loop: each feature was written, then reviewed by a
separate pass plus an adversarial verifier whose job was to break it. That
caught real bugs before release, including a cross-layer symlink that let a
read-scoped token reach another layer, and an `rmtree` that followed symlinks.
The suite has 100+ tests and is linted clean.

## Develop

```bash
uv sync           # create venv (Python 3.12) + install deps
uv run pytest     # tests
uv run ruff check .
uv run mem-server # run the MCP server over stdio
```

## License

[MIT](LICENSE)

TDQS

A3.6/5.0

Scored across 8 tools

Disambiguation5/5

Each tool serves a distinct purpose: file operations (delete, read, write), skill management (get_skill, put_skill, list_skills), and discovery (list, search). No two tools have overlapping functionality.

Naming Consistency4/5

Names are lowercase with underscores for multi-word tools (e.g., get_skill), but single-word names like delete and read break the pattern slightly. Still, the naming is predictable and readable.

Tool Count5/5

8 tools cover core operations for memory files and skills without bloat. The set is well-scoped for the stated purpose.

Completeness4/5

Covers create/read/delete for files and CRUD for skills except delete_skill. Write presumably overwrites, acting as update. Minor gap: no dedicated skill deletion tool.

Maintenance

ActivityInactive
ResponsivenessNo issues