Metadata-Version: 2.4
Name: erudition-mcp
Version: 0.1.0
Summary: Portable, typed, git-friendly memory for AI agents: an MCP server over a markdown vault.
Author: Ralf090102
License: MIT
Project-URL: Homepage, https://github.com/Ralf090102/Erudition
Project-URL: Repository, https://github.com/Ralf090102/Erudition
Project-URL: Issues, https://github.com/Ralf090102/Erudition/issues
Keywords: mcp,model-context-protocol,claude,agent-memory,markdown,obsidian
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: mcp<2.0.0,>=1.0.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Dynamic: license-file

# Erudition

Portable, typed, git-friendly memory for AI agents.

Claude Code's built-in memory is local-only and opaque. It lives inside Claude Code itself, isn't portable across machines, and isn't something you can casually read, diff, or edit by hand. Erudition is an [MCP](https://modelcontextprotocol.io) server that gives an agent read/write access to a memory store that's none of those things: a plain folder of markdown files, organized around a real taxonomy instead of a junk drawer, editable in whatever notes app you already use, and portable across every machine you own via git.

This README covers what Erudition does and how to install it. For the reasoning behind why it's built this way, and just as importantly, why it isn't built several other, more obvious ways, see [WHY.md](./WHY.md).

## What it is

- **An MCP server.** Works with Claude Code, Claude Desktop, or any other MCP-compatible client, exposing nine tools (see [Tools](#tools) below).
- **A markdown vault, not a database.** Every memory is a `.md` file with YAML frontmatter, in a folder you fully control. No proprietary format, no service to run, no account to create. Obsidian is a good viewer/editor for it, but Erudition doesn't require Obsidian. Any plain-text editor works.
- **A taxonomy, not a junk drawer.** Memories are typed (`user` / `feedback` / `project` / `reference` by default), so an agent (and you) can tell "a fact about how I like to work" apart from "a fact about this specific project" at a glance.

## What it isn't

- Not a hosted product. It runs locally, wherever your agent runs.
- Not a "chat with your notes" RAG plugin. Erudition is about agent *memory* (what an agent should remember across sessions), not general document search.
- Not a replacement for Claude Code (or Claude Code's own memory). It's a layer on top, and it's designed to stay small rather than grow into a competing agent runtime.

## Quickstart

### 1. Install

```bash
git clone https://github.com/Ralf090102/Erudition.git
cd Erudition
pip install -e .
```

This installs the `erudition` command (the PyPI distribution is named `erudition-mcp`; the import path and CLI command stay `erudition`).

### 2. Point it at a vault

Erudition needs to know which folder is your vault:

```bash
export ERUDITION_VAULT_PATH="/path/to/my-notes"
```

That's a complete, valid setup on its own. If you want the auto-commit toggle or a custom taxonomy, write an `erudition.toml` instead (or alongside). Copy [`erudition.example.toml`](./erudition.example.toml) and see the [Configuration](#configuration) table below for every option.

### 3. Connect it to your MCP client

For Claude Code, add it to your MCP server config:

```json
{
  "mcpServers": {
    "erudition": {
      "command": "erudition",
      "env": {
        "ERUDITION_VAULT_PATH": "/path/to/my-notes"
      }
    }
  }
}
```

Restart your client. You should see all nine tools below available.

Erudition keeps a small search index at `{vault}/.erudition/`. It's a rebuildable cache, not real content, so add `.erudition/` to the vault's own `.gitignore`.

## Tools

| Tool | What it does |
|---|---|
| `read_note(path)` | Read a note's contents. |
| `list_notes(folder="")` | List markdown notes in a folder, recursively (empty string lists the whole vault). |
| `search_notes(query, folder="")` | Search note contents, backed by a rebuildable SQLite FTS5 index rather than a linear scan. This makes matching token-based, not raw substring: `wid*` matches "widgets," a bare `idget` doesn't the way a plain substring scan would. Supports FTS5 phrase (`"exact phrase"`) and boolean (`AND` / `OR` / `NOT`) syntax. Falls back automatically to a plain linear scan if the local `sqlite3` build lacks FTS5. Returns matching file paths and the lines that matched; if a query is still slow despite the index, the result includes a one-line note suggesting the staleness check itself might be worth revisiting. |
| `write_note(path, content, overwrite=False, dry_run=False)` | Write a note; refuses to clobber an existing one unless `overwrite=True`. `dry_run=True` writes nothing and returns a preview (new content, or a diff against what's there) instead. |
| `append_to_note(path, content, dry_run=False)` | Append to a note, creating it (and any parent folders) if it doesn't exist yet. |
| `list_memory_types()` | List the vault's configured taxonomy: valid `type` values for `save_memory`, and what each is for. |
| `save_memory(type, name, description, content, overwrite=False, dry_run=False)` | Save a typed memory (validated against the taxonomy), with standard frontmatter, indexed in `MEMORY.md`. |
| `sync_memory()` | Mirror the vault's memory folder out to a configured local memory directory. |
| `rebuild_search_index()` | Force a full rebuild of the search index from scratch. Rarely needed, since `search_notes` already self-heals from a cheap per-search staleness check; mostly a manual escape hatch for recovery or peace of mind after a large batch of external edits. |

## The taxonomy

Erudition's default memory types, and what each is for:

| Type | What goes here |
|---|---|
| `user` | Who the person is: their role, goals, expertise. Lets an agent tailor how it explains things. |
| `feedback` | Corrections and confirmed approaches, like "don't do X, we tried it and it broke Y" or "yes, that approach was right." Stops an agent from repeating a mistake or re-litigating a settled call. |
| `project` | Ongoing context about *why* (decisions, deadlines, stakeholders) that isn't visible in the code or git history alone. |
| `reference` | Pointers to where information actually lives, e.g. "bugs are tracked in Linear project X," not a copy of the bugs themselves. |

This is a starting point, not a fixed schema. Define your own types under `[taxonomy.*]` in `erudition.toml` if your vault needs a different shape. See `erudition.example.toml`.

## Saving and syncing memories

Beyond generic `write_note`, Erudition understands the taxonomy. `save_memory` validates the `type` you pass against the vault's configured types, writes the memory to `Memory/{type}_{slug}.md` with standard frontmatter, and keeps `Memory/MEMORY.md` as a lightweight index, one line per memory:

```python
save_memory(
    type="feedback",
    name="Prefers terse commit messages",
    description="User wants one-line commit messages, no bullet lists.",
    content="Confirmed after three separate corrections. Applies to this repo only.",
)
```

If your agent also keeps its own local memory folder (Claude Code does), `sync_memory()` mirrors everything under the vault's memory folder out to it, so writing a memory once in the vault doesn't also require a manual second write elsewhere. Configure the destination via `local_memory_dir` (see Configuration below). `sync_memory()` errors clearly if it isn't set, rather than silently doing nothing.

## Write safety: a permanent design choice, not a limitation

Erudition assumes **one active writer at a time.** Flat markdown files have no locking, so if a human and an agent (or two agent sessions) write to the same note within moments of each other, the later write silently wins. No merge, no conflict marker, no error.

This is deliberate, not an oversight. Closing that gap completely would mean row-level locking, which means becoming a database. That trades away the one thing that makes a markdown vault worth using over a SQLite-backed alternative: you can open, read, and edit every memory in any plain-text editor, no tooling required. Instead, Erudition offers durability without locking: an optional `auto_commit` setting that git-commits every write with a distinct, identifiable message (`erudition: write <path>`). It doesn't prevent a collision, but it means a collision is always recoverable via `git log` / `git diff` instead of silently gone.

If you run more than one agent session against the same vault at the same time, that's exactly the scenario this boundary is about. Treat "one writer at a time" as a real constraint, the same way you would with any file two processes might edit concurrently.

Separately from durability, `write_note`, `append_to_note`, and `save_memory` all accept `dry_run=True`: nothing gets written, and you get back a preview instead, either the full content for a brand-new note, or a unified diff against what's already there for an overwrite. Useful for checking what a change would actually do before committing to it, especially with `overwrite=True`.

## Optional: Claude Code hook integration

This is Claude-Code-specific and entirely optional — it's a companion to the core MCP server above, not a change to it, and it doesn't do anything with other MCP clients. If you don't use Claude Code, skip this section.

Two problems this solves: remembering to read a "where we left off" note at the start of every session, and remembering to call `sync_memory()` after making changes. Claude Code has its own hook system (separate from MCP) for exactly this: `SessionStart` can inject context automatically when a session begins, and `Stop` runs after every turn.

Add this to your project's `.claude/settings.json`:

```json
{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "erudition hook session-start"
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "erudition hook stop"
          }
        ]
      }
    ]
  }
}
```

`erudition hook session-start` reads the note at `session_start_note` (defaults to `Memory/CURRENT.md`; see [Configuration](#configuration)) and injects its contents as session context. `erudition hook stop` calls `sync_memory()`. Both are best-effort: a missing note or an unconfigured `sync_memory` is a silent no-op, never an error that could block a session or a turn.

## Configuration

| Setting | Env var | `erudition.toml` key | Default |
|---|---|---|---|
| Vault path | `ERUDITION_VAULT_PATH` | `vault_path` | *(required, one or the other)* |
| Auto-commit on write | `ERUDITION_AUTO_COMMIT` | `auto_commit` | `false` |
| Memory taxonomy | — | `[taxonomy.*]` | the four types above |
| Memory folder | `ERUDITION_MEMORY_FOLDER` | `memory_folder` | `Memory` |
| Local memory mirror (for `sync_memory`) | `ERUDITION_LOCAL_MEMORY_DIR` | `local_memory_dir` | unset |
| Session-start note (for the optional hook) | `ERUDITION_SESSION_START_NOTE` | `session_start_note` | `Memory/CURRENT.md` |
| Config file location | `ERUDITION_CONFIG` | — | `./erudition.toml` if present |

An env var always wins over the config file for the setting it covers. Full example with every option: [`erudition.example.toml`](./erudition.example.toml).

## Development

```bash
pip install -e ".[dev]"
pytest
```

## Status

Early stage. Config-driven vault path, generalized taxonomy, the write-safety toggle (including dry-run previews), the taxonomy-aware memory tools (`save_memory`, `sync_memory`, `list_memory_types`, `append_to_note`), a rebuildable FTS5 search index, and a real, repo-tracked test suite are all done. Not yet on PyPI. Expect breaking changes before a 1.0 release.

## License

MIT. See [LICENSE](./LICENSE).
