mor
by laat
README.md
# mor
> [!WARNING]
> This is a personal project. I maintain it for my own use and share it because others might find it useful. Feature PRs are unlikely to be merged — if you have an idea, [start a discussion](https://github.com/laat/mor/discussions) first. Fork freely — it's MIT licensed.
AI-accessible knowledge you actually own. **Plain markdown files on your disk**, searchable by AI via MCP.
Your notes live on your disk as plain markdown with YAML frontmatter — readable without mor, portable to any tool, git-syncable across machines. The MCP server gives AI assistants (Claude Code, Claude Desktop, Cursor, etc.) persistent memory that survives context windows. You also get a CLI and HTTP API.
## Install
```sh
npm install -g mor
```
Requires Node.js 20+.
## Quick start
```sh
# Add notes
echo "Always use snake_case for Python variables" | mor add -t "Python naming"
mor add notes.md -t "Meeting notes" --tags "meeting,project-x"
mor add https://raw.githubusercontent.com/owner/repo/main/config.ts
# Search (FTS5 — tokenized, stemmed)
mor find python naming
# Grep (literal substring or regex)
mor grep snake_case
mor grep -i todo
mor grep -E "async\s+function"
mor grep -w Beer -n -C 2
# Read, edit, copy, remove
mor cat python naming
mor edit python naming
mor cp -o ./out.md python naming
mor rm python naming
# List
mor ls
mor ls -l
```
## Commands
| Command | Description |
| ----------------- | --------------------------------------------------------------------------------------------------------------- |
| `find <query>` | Full-text search (`--limit`, `-s` threshold, `--json`) |
| `grep <pattern>` | Substring or regex search (`-i`, `-E` regex, `-w` word, `-n` line numbers, `-l` files only, `-A/-B/-C` context) |
| `add [file\|url]` | Add from file, URL, stdin, or `$EDITOR` (`-t` title, `-d` description, `--tags`, `--type`) |
| `cat <query>` | Print content (`--raw` for frontmatter, `--links` for cross-references) |
| `cp <query...>` | Copy content to file (`-o <dest>`) |
| `edit <query>` | Open in `$EDITOR` (`--raw` to edit frontmatter) |
| `update <query>` | Update metadata or content (`-t` title, `-d` description, `--tags`, `--type`, `--content-from`) |
| `patch <query>` | Apply a `str_replace` patch to a note's content (`--old`, `--new`) |
| `rm <query>` | Remove a note |
| `links [query]` | Show cross-references for a note (`--broken` to find dangling links) |
| `ls` | List all (`--limit`, `-l` long, `--tags`, `--types`) |
| `sync` | Pull, commit, and push the notes folder via git |
| `reindex` | Rebuild search index |
| `import <dir>` | Import `.md` files from a directory |
| `mcp` | Start MCP server (stdio) |
| `serve` | Start HTTP server (`-p` port, `-H` host, `--token`, `--mcp`) |
| `login` | Authenticate with a remote server via OAuth (`-s` server URL) |
Queries resolve in order: full UUID, UUID prefix (8+ chars), filename, FTS search. Multi-word queries don't need quoting — options go before the query: `mor find --limit 5 python naming`.
`find`, `grep`, and `ls` support shared filters: `--type`, `--tag`, `--repo`, `--ext` (all support glob patterns).
## MCP server
Add to your Claude Code or Claude Desktop config:
```json
{
"mcpServers": {
"mor": {
"command": "mor",
"args": ["mcp"]
}
}
}
```
Tools: `notes_search`, `notes_read`, `notes_create`, `notes_update`, `notes_patch`, `notes_remove`, `notes_list`, `notes_grep`.
To make sure Claude Code checks mor first when you ask it to recall something, add this to `~/.claude/CLAUDE.md`:
```markdown
## Notes
When the user asks to recall, find, check, or reuse something they previously saved
or remembered — use the `mor` MCP server tools (`notes_search`, `notes_read`,
`notes_list`). This is the user's primary note store containing code snippets,
files, and reference notes. Always check mor before saying something wasn't found.
```
## Remote access
Run the server on one machine, access from anywhere:
```sh
# Server
mor serve --port 7677 --token mypassphrase --mcp
```
### MCP clients (Claude Code, Claude Desktop, etc.)
Point your MCP client at the server URL — no secret in the config:
```json
{
"mcpServers": {
"mor": {
"type": "url",
"url": "http://mybox.tail1234.ts.net:7677/mcp"
}
}
}
```
The client discovers auth via `WWW-Authenticate` → OAuth metadata → browser passphrase flow, all automatic.
### CLI client
```sh
# OAuth login — saves server URL to config and credentials to credentials.json
mor login -s http://mybox.tail1234.ts.net:7677
# All commands now proxy to the remote server
mor find "python naming"
```
Or configure a direct token instead:
```jsonc
// ~/.config/mor/config.json
{
"server": {
"url": "http://mybox.tail1234.ts.net:7677",
"token": "mypassphrase",
},
}
```
OAuth tokens auto-refresh on expiry.
### Authentication
When `--token` is set, all routes require auth. Two methods work everywhere:
- **Bearer token**: `Authorization: Bearer <passphrase>` or `?token=<passphrase>`
- **OAuth access token**: obtained via the OAuth flow (`mor login` or MCP client auto-discovery)
Unauthenticated requests get a `401` with a `WWW-Authenticate` header pointing to the OAuth discovery endpoint.
### HTTP API
| Method | Path | Description |
| -------- | --------------------------------------------------------- | -------------------------------------------------------------------- |
| `GET` | `/health` | Health check |
| `GET` | `/notes?limit=N&offset=N` | List all |
| `GET` | `/notes/search?q=...&limit=N&offset=N` | FTS search |
| `GET` | `/notes/grep?q=...&limit=N&offset=N&ignoreCase=1®ex=1` | Substring or regex search |
| `GET` | `/notes/:query` | Read one |
| `GET` | `/notes/:query/links` | Get forward and backlinks |
| `POST` | `/notes` | Create (`{title, content, description?, tags?, type?, repository?}`) |
| `PUT` | `/notes/:query` | Update (`{title?, description?, content?, tags?, type?}`) |
| `POST` | `/notes/:query/patch` | Patch content (`{old_str, new_str}`) |
| `DELETE` | `/notes/:query` | Remove |
| `POST` | `/reindex` | Rebuild the search index |
| `POST` | `/sync` | Git pull, commit, push |
| `POST` | `/mcp` | MCP protocol (streamable HTTP) |
## Embeddings
Optionally augment FTS search with vector similarity. Configure in `config.json`:
```json
{
"embedding": {
"provider": "openai",
"model": "text-embedding-3-small",
"dimensions": 1536
}
}
```
Providers: `openai` (or compatible API via `baseUrl`), `azure-openai`, `ollama`. Run `mor reindex` after configuring.
Azure OpenAI uses `AZURE_OPENAI_API_KEY` (or `apiKey` in config) and requires a `deployment` name (defaults to model name).
## Storage
Notes are markdown files with YAML frontmatter, split across [XDG directories](https://specifications.freedesktop.org/basedir-spec/latest/). Set `MOR_HOME` for a single flat directory.
```
~/.config/mor/ # config
config.json
~/.local/share/mor/ # data
notes/
python-naming-a1b2.md
meeting-notes-c3d4.md
~/.local/state/mor/ # state
index.db # search index
credentials.json # OAuth tokens (mor login)
oauth.db # OAuth server tokens
```
Files are human-readable and git-friendly. Use `mor sync` to pull, commit, and push if the notes folder is a git repo. Enable `autosync` to sync automatically after every add, update, or remove:
```json
{
"autosync": true
}
```
## License
MIT
This server cannot be deployed
Maintenance
ActivityActive
ResponsivenessNo issues