← Discover MCPs and Agents
m
AgentAI & MLGitHub

mora

Experimental local-first memory CLI for MCP agents: read-only Gmail, calendars, iMessage, and files → cited local context

Links

README

From the repo.

Mora, the all-remembering eye

Mora

Search your mail, messages, calendars, files, and GitHub issues from one place on your computer.

CI Release License Go Corpus

[!WARNING] Mora is alpha software. We use it every day, and its main paths have tests. It has not been tested on many other people's real data. Read the cited source before you act on a result. A failed or old sync can make the local copy stale.

Mora reads the sources you choose and saves copies as Markdown files on your computer. You can search them yourself or let an AI agent search them through MCP (a way for an agent to call local tools). Mora supports:

  • Gmail
  • Google Calendar
  • iMessage on macOS
  • WhatsApp Desktop on macOS
  • Apple Calendar on macOS
  • folders and files that you choose
  • GitHub Issues from repositories that you choose

The source connectors do not change the original mail, messages, events, or issues. Mora builds a SQLite index on your computer so searches are fast. Claude Code, Codex, and other MCP clients can use the same saved data.

Mora does not call an AI model by default. It finds source records and builds briefs that link back to them. Your agent can then write an answer from that evidence. If you want, a local Ollama model can improve search.

Jump to: Install · Add a source · Connect an agent · Update · Privacy

Read-only sources flow into a local Markdown vault and SQLite index, then into any MCP client. Backup and sharing are optional network paths.

Start on macOS

You need macOS and Homebrew for this route. The Cask installs the signed Mora.app and adds the mora command:

brew tap pyranthus-hq/tap
brew install --cask pyranthus-hq/tap/mora
mora version

This app contains the memory CLI. It does not include the separate desktop companion. If Mora is already installed, read the migration steps before using Brew.

If you do not use Homebrew, install the same signed app directly in ~/Applications/Mora.app:

(
  set -e
  mora_installer="$(mktemp -t mora-install)"
  trap '/bin/rm -f "$mora_installer"' EXIT
  curl -fsSLo "$mora_installer" https://raw.githubusercontent.com/pyranthus-hq/mora/main/install-app.sh
  sh "$mora_installer"
)

The direct installer checks the release and links mora to the app. Neither install method sets up connectors or a daily update job. See Keep Mora up to date when you are ready.

Other systems

Linux and older standalone installs can use:

curl -fsSL https://raw.githubusercontent.com/pyranthus-hq/mora/main/install.sh | sh

Windows users should see the Windows guide. To build from source with Go 1.25 or later:

go install github.com/pyranthus-hq/mora/cmd/mora@latest

Source builds report version dev. They do not update themselves. Google also needs your own OAuth client when you build from source.

Add your first source

A folder is the quickest way to check that Mora works. It needs no account login. Replace ~/Documents/notes with a folder you choose:

mora init
mora connect filesystem ~/Documents/notes
mora search "a project or person"

After you connect a source, try searching for a decision or asking your agent “What is on my calendar next week?” Search reads saved data. Writing a new memory is a separate action controlled by your MCP write policy.

Then add only the sources you want:

mora connect google                         # Gmail and Google Calendar
mora connect github --repo owner/repository # GitHub Issues
mora connect imessage                       # macOS; needs Full Disk Access
mora connectors enable whatsapp             # macOS; local read-only store
mora ingest run --source whatsapp

For Apple Calendar:

mora connectors enable applecalendar
mora ingest run --source applecalendar

Setup commands do different jobs:

CommandWhat it does
connectSets up a source, enables it, and gets its first data.
connectors enableGives Mora permission to use a connector. It does not promise a data pull.
ingest runReads enabled sources and writes their current data into the vault. Use it for a first load or a backfill.
syncRefreshes a source that is already set up.

Connect an AI agent

claude mcp add mora -s user -- mora mcp serve
codex mcp add mora -- mora mcp serve

Other MCP clients can start the same command:

{
  "mcpServers": {
    "mora": { "command": "mora", "args": ["mcp", "serve"] }
  }
}

MCP tools cover search, reading, writing, briefs, meetings, and people. The command line also handles setup and maintenance.

Mora also publishes an experimental Agent Plugins 1.0 package that bundles the stdio MCP declaration with portable Agent Skills. It does not install Mora, grant source permissions, or sandbox the client. Enabling it may auto-start the local MCP server, so review the client's data policy and choose mora config mcp-write-policy propose or readonly before first use.

Check health and add a schedule

mora doctor
mora schedule install ingest-hourly
mora schedule install pulse-daily
mora schedule list

On macOS, jobs from a signed app launch through Mora.app. This lets macOS use the app's Full Disk Access identity. Mora records its own sync result because the macOS app launcher does not return the inner command's exit code.

Full Disk Access on macOS

iMessage, WhatsApp, and Apple Calendar are local, but macOS still protects their files. Mora cannot grant this permission for you.

  1. Install the signed Mora.app first.
  2. Open System Settings.
  3. Open Privacy & Security, then Full Disk Access.
  4. Press + and choose /Applications/Mora.app for a Homebrew install, or ~/Applications/Mora.app for a direct install. Press Command-Shift-G to enter the path if needed.
  5. Turn Mora on. If macOS asks, quit and reopen the app or terminal.
  6. Run mora doctor.
  7. Run mora sync imessage, mora sync whatsapp, or mora sync applecalendar.

If an old Mora entry is present, keep it until the new app passes mora doctor and a protected sync. Then remove the old entry yourself. After an update, repeat those checks and grant access again if macOS asks. Never replace only the executable inside Mora.app; that breaks its signature. The guide covers older installations.

Ask an agent to set it up

Copy this prompt into an agent that can run local shell commands:

Install Mora from the official pyranthus-hq/mora repository and set up a small,
safe first run. On macOS, use the signed Mora.app Homebrew Cask, or the direct
signed-app installer if I do not use Homebrew. Verify `mora version` and run
`mora doctor`. Ask me before any Google
OAuth approval, GitHub token use, Full Disk Access change, backup, sharing, or
schedule install. Do not say you clicked or approved a system screen. I will do
those steps myself. Start with one folder that I choose, connect it, run a test
search, then offer to wire `mora mcp serve` into my agent. Report every command,
what it changed, and any check that did not pass.

Use Mora every day

mora brief                                      # what changed and what matters
mora search "What is open with Sam?"           # direct recall
mora think "What did we decide about pricing?" # evidence plus gaps for an agent
mora write --scope project:acme --type decision \
  --title "OAuth" --text "Use PKCE."            # save a decision
mora brief --event-id calendar_event/abc        # cited meeting prep
mora tasks list                                 # open local tasks
mora doctor                                     # source and index health

Useful flows:

  • Start a work session with mora brief.
  • Search for a person, project, issue, or decision with mora search.
  • Use mora think when the answer needs several pieces of evidence.
  • Save a note, fact, decision, or insight that you want agents to remember with mora write.
  • Use mora brief --event-id <id> before a meeting.
  • Capture and close small local tasks with mora tasks add, list, and done.
  • Run mora doctor when results look old or incomplete.

Mora can install Claude Code hooks too:

mora hook install
mora hook status

The SessionStart hook adds the brief. The UserPromptSubmit hook adds a small set of related memories for each prompt. Mora keeps existing valid Claude hooks. It refuses to rewrite a settings file that it cannot parse.

Correct or remove memory

Mora can propose that two addresses belong to the same person. It never accepts the match on its own.

mora teach identity list
mora teach identity confirm --handle <phone> --email <address> --yes
mora teach identity reject --handle <phone> --email <address>

You can correct a cited commitment or a note that you wrote:

mora teach commitment wrong-direction --memory-id <id> --direction owed_by_self --yes
mora teach commitment already-closed --memory-id <id> --yes
mora teach memory correct --id <id> --title "Correct title" --text "Correct text" --yes
mora teach history --memory-id <id>
mora teach undo <ledger-id>

mora delete removes one memory now. A later source sync can restore connector data. Use mora forget when the removal must remain after sync:

mora forget --chat <stable-id> --dry-run
mora forget --chat <stable-id> --yes
mora forget list
mora unforget <entry-id> --yes

Forget changes only Mora's local copy. It never deletes the source message, event, or issue.

Backup and sharing are separate choices

Mora offers three different paths:

PathLeaves this computer?Encryption added by Mora?Use
mora backupNoNoMake a local .tar.gz copy in Mora's state directory.
mora sync gitYes, if the remote is off-deviceNoPush the plaintext vault to a private git remote you control.
mora shareYesYes, with ageSend only authored memories from one scope through private git or your S3/R2 bucket.

The vault contains plaintext. A private git remote is private by access rules, not by Mora encryption. mora share encrypts before upload, but the remote can still reveal file sizes and update timing. A person who already downloaded a share keeps that copy.

See the guide before you enable a network path.

Browser access on this computer

An agent that cannot start a local process can use Mora's loopback HTTP server:

mora serve http
# or keep it running as a user service
mora serve http install
mora serve http status

It binds only to 127.0.0.1, uses a bearer token, and does not enable CORS. Loopback means this computer only. It is still a local security boundary: any process that gets the token can call the allowed routes. The generic HTTP call route does not expose delete_memory.

Control writes from MCP

mora config mcp-write-policy open
mora config mcp-write-policy propose
mora config mcp-write-policy readonly
  • open lets the connected agent write and delete. This is the default.
  • propose stores write proposals for local approval. It refuses deletes.
  • readonly refuses writes and deletes.

Review proposals with mora mcp proposals list, approve, and reject.

Durable loops

Long-running automation can record a lease and a result. This prevents two workers from doing the same scheduled work at once.

mora loop register daily-report --cadence daily --command "my-report-command"
mora loop begin daily-report
mora loop heartbeat daily-report --run <run-id>
mora loop done daily-report --run <run-id> --ok
mora loop status daily-report
mora loop list

The built-in daily brief uses this system. A crash can leave a run marked uncertain. Inspect it before you repeat an outside action.

Keep Mora up to date

Choose the command for your install method. A new release must be published before either route can install it.

Installed withUpdate command
Homebrew Caskbrew update then brew upgrade --cask pyranthus-hq/tap/mora
Direct signed-app installermora upgrade
Go source buildRun go install again. Source builds do not self-update.

Installing the app does not set up automatic checks. To enable Mora's daily update job for a signed app, run:

mora upgrade --policy auto
mora schedule install update-daily
mora schedule list
mora upgrade --status

The first command saves the policy; the second installs the job. notify checks for releases and reminds you instead of installing them. off stops scheduled checks. A Brew-managed app may need a manual brew upgrade if Mora cannot replace its app bundle. The update guide explains the checks, rollback, and recovery path.

After any update, run mora version and mora doctor. If you use iMessage, WhatsApp, or Apple Calendar, also run a sync for that source to check that macOS still lets Mora read it.

Uninstall

mora schedule list
mora schedule uninstall <each-job-name-shown>
mora hook uninstall
mora serve http uninstall

Remove the jobs shown by mora schedule list before you uninstall the app. Then use brew uninstall --cask pyranthus-hq/tap/mora for a Brew install, or the checked uninstall-app.sh command in the guide for a direct install. Both leave your saved data in place; neither removes scheduled jobs for you.

Data layout

PathHoldsCan Mora rebuild it?
~/vault/moraMarkdown memoriesNo. Back it up.
~/.local/share/moraSQLite index and received sharesYes, except received share data must be pulled again.
~/.local/state/moraSync state, local usage log, local backupsUsually.
~/.config/moraSettings, OAuth tokens, share keysNo. Reconnect or restore it.

Run mora config to see the active paths. MORA_VAULT can select another absolute vault path for one process. It does not change config.toml.

Privacy boundary

  • Source access is read-only. Google uses gmail.readonly and calendar.readonly. iMessage uses mode=ro. Apple Calendar uses mode=ro plus SQLite query_only(1) so it can read the live write-ahead log safely.
  • The vault, index, tokens, sync state, and usage log stay local by default.
  • Mora uses the network for source APIs, GitHub release checks, and any backup or share target that you choose.
  • Optional Ollama embeddings are allowed only on a loopback address.
  • Mora's local usage log leaves out query text by default. Turn it off with mora usage off or DO_NOT_TRACK=1.
  • A cloud agent is a separate boundary. After it reads a Mora result, its model provider and data rules apply.
  • Files are plaintext on disk. Use FileVault, BitLocker, or other disk encryption. Do not put the vault in an unencrypted remote.

Search and proof

With the default static embedder, Mora searches parent memories and bounded Gmail/iMessage message segments with full-text search. With an active semantic Ollama embedder, it combines full-text, vector, person-graph, and message-segment results with Reciprocal Rank Fusion. That name means it joins ranked lists by position instead of comparing unrelated raw scores.

Mora's frozen test corpora start from a written answer key. Tests render mail, messages, and events from that key, pin every byte, run the real brief and meeting code, and check citations and commitment fields. Mutation tests also break each important gate on purpose and require a test to fail. This is strong proof against known regressions. It is not proof that every real inbox or calendar will work.

The fixtures and scores are in internal/mora/eval/. The method is in evaluation and testing.

Source receipts include the observation and attempt times, last success, next expected run, duration, freshness budget, consecutive failures, and a correlation ID. A source cannot report fresh unless its observation and last success both fall inside that budget. Search evidence manifests carry the ingest correlation ID for each cited memory plus a deterministic query ID; sanitized stage events live only in Mora's state directory under observability/traces.jsonl. Doctor uses the same ID as its diagnostic evidence link. Trace events never contain source content or local paths.

More

  • Guide — commands, connectors, and upkeep.
  • Architecture — current design and code map.
  • Contributing — build and review rules.
  • Security — report a problem privately. Never paste vault data or credentials into a public issue.

Named for Hermaeus Mora, keeper of knowledge and memory.

Collected info

  • 9 stars
  • Language: Go
  • Source updated: 9/11/2026