Назад к MCP
🐍 PythonMCP ServerKnowledge & MemoryLocal

Каталог MCP

agentic-mcp-tools/memora

Persistent memory with knowledge graph visualization, semantic/hybrid search, cloud sync (S3/R2), and cross-session context management.

Открыть GitHub
Рейтинг
715
Звёзды GitHub
янв. 2026 г.
Добавлено
Сообщество
Статус

Описание

<h1 align="center"><img src="media/memora_new.gif" width="60" alt="Memora Logo" align="absmiddle"> Memora</h1> <p align="center"><sub><sub><i>"You never truly know the value of a moment until it becomes a memory."</i></sub></sub></p> <p align="center"> <b>Give your AI agents persistent collective memory</b><br> An MCP memory layer for agents: structured storage, semantic retrieval, graph relations, and source-backed cross-session context. </p> <p align="center"> <a href="https://github.com/agentic-box/memora/releases"><img src="https://img.shields.io/github/v/tag/agentic-box/memora?label=version&color=blue" alt="Version"></a> <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="License"></a> <a href="https://github.com/thedotmack/awesome-claude-code"><img src="https://awesome.re/mentioned-badge.svg" alt="Mentioned in Awesome Claude Code"></a> </p> <p align="center"> <img src="media/memora-absorb-digest-flow.gif" alt="Memora absorb and digest flow" width="820"> </p> <p align="center"> <b>Absorb agent work into durable graph memory, then use <code>memory_digest(topic)</code> to retrieve relevant memories, TODOs/issues, related edges, and source IDs.</b> </p> <p align="center"> <b><a href="#features">Features</a></b> · <b><a href="#preview">Preview</a></b> · <b><a href="#install">Install</a></b> · <b><a href="#usage">Usage</a></b> · <b><a href="#configuration">Config</a></b> · <b><a href="#multi-database-routing">Multi-DB</a></b> · <b><a href="#container-deployment">Containers</a></b> · <b><a href="#live-graph-server">Live Graph</a></b> · <b><a href="#cloud-graph">Cloud Graph</a></b> · <b><a href="#chat-with-memories">Chat</a></b> · <b><a href="#semantic-search--embeddings">Semantic Search</a></b> · <b><a href="#document-storage">Documents</a></b> · <b><a href="#llm-deduplication">LLM Dedup</a></b> · <b><a href="#memory-linking">Linking</a></b> · <b><a href="#neovim-integration">Neovim</a></b> </p>

Features

Core Storage

  • 💾 Persistent Storage - SQLite with optional cloud sync (S3, R2, D1)
  • 🗄️ Multi-database routing - One process serves many stores; a workspace reaches its own at /mcp/<name> (see Multi-database routing)
  • 📂 Hierarchical Organization - Section/subsection structure with auto-hierarchy assignment
  • 📦 Export/Import - Backup and restore with merge strategies

Absorb & Lineage

  • 🧬 Absorb - Feed facts in; an LLM classifies each against the store (duplicate / update / contradiction / related / new), skips duplicates, links relations, and consolidates related facts — with dry_run preview
  • 🌱 Supersession Lineage - Updates supersede old knowledge instead of deleting it; retrieval follows the chain to the current version by default (follow modes: active, latest, full_history)
  • 🗞️ Topic Digest - memory_digest(topic) bundles relevant memories, open TODOs/issues, related edges, and source IDs into one retrieval

Search & Intelligence

  • 🔍 Semantic Search - Vector embeddings (TF-IDF, sentence-transformers, OpenAI)
  • 🎯 Advanced Queries - Full-text, date ranges, tag filters (AND/OR/NOT), hybrid search
  • 🔀 Cross-references - Auto-linked related memories based on similarity
  • 🤖 LLM Deduplication - Find and merge duplicates with AI-powered comparison
  • 🔗 Memory Linking - Typed edges, importance boosting, and cluster detection

Document Storage

  • 📄 Structured Documents - Store markdown documents as searchable fragment trees (claims, plan items, references, risks)
  • 🔒 Fragment Integrity - Guards against accidental delete/merge/absorb of document fragments
  • 🔍 Granular Search - Individual claims and findings are semantically searchable while the full document remains retrievable as a unit

Tools & Visualization

  • Memory Automation - Structured tools for TODOs, issues, and sections
  • 🕸️ Knowledge Graph - Interactive visualization with Mermaid rendering and cluster overlays
  • 🌐 Live Graph Server - Built-in HTTP server with cloud-hosted option (D1/Pages)
  • 💬 Chat with Memories - RAG-powered chat panel with LLM tool calling to search, create, update, and delete memories via streaming chat
  • 📡 Event Notifications - Poll-based system for inter-agent communication
  • 📊 Statistics & Analytics - Tag usage, trends, and connection insights
  • 🧠 Memory Insights - Activity summary, stale detection, consolidation suggestions, and LLM-powered pattern analysis
  • 📜 Action History - Track all memory operations (create, update, delete, merge, boost, link) with grouped timeline view

Preview

<p align="center"> <img src="media/demo.gif" alt="Memora memory graph demo" width="320"> <img src="media/demo2.gif" alt="Memora memory interaction demo" width="320"> </p>

Install

Two paths. pip is a local stdio child the client spawns. A container is a detached HTTP service you start with up; with MEMORA_DATABASES it serves multiple stores from one process. The LaunchAgent supervises the proxy, not the container — after a host restart the listener can come back while its upstream is still stopped. If you are running memora as a service, the container path is the install.

pip (local / stdio)

pip install memora-mcp

The PyPI package is memora-mcp (bare memora on PyPI is an unrelated project). Includes cloud storage (S3/R2) and OpenAI embeddings out of the box.

# Optional: local embeddings (offline, ~2GB for PyTorch)
pip install "memora-mcp[local]"

# Latest development version straight from git
pip install "git+https://github.com/agentic-box/memora.git"

Then spawn it from .mcp.json with "command": "memora-server" (see Configuration).

Container (HTTP service)

Default runtime is Apple's container CLI. Every container operation scripts/memora-instance.sh performs (build, up, status, logs, down) uses $MEMORA_CONTAINER_BIN (default container). The generated proxy process does not; it hardcodes container list.

Before the first build:

  1. Install Apple's container CLI (signed pkg from its GitHub releases). It needs a Mac with Apple silicon running macOS 26 — Apple does not support older macOS versions for container.

  2. Start the runtime — Apple's documented first command, which also installs a kernel if none is configured:

    container system start
    
  3. Clone this repo and cd into it:

    git clone https://github.com/agentic-box/memora.git
    cd memora
    
  4. Copy the instance template. It ships with INSTANCE=myinstance so the later build/up/proxy lines match without renaming. Edit PORT and a backend (STORAGE_URI, VOLUME, or MEMORA_DATABASES):

    cp instances/example.env instances/myinstance.env
    
  5. Create the credential file and install the proxy the LaunchAgent will run. cred_args() requires a .mcp.json whose mcpServers.memora.env holds CLOUDFLARE_API_TOKEN (D1 access) and the embedding/LLM keys — up dies if that file is missing. The script looks for ~/.config/memora/credentials.mcp.json if that file exists, otherwise ~/repos/agentic-box/.mcp.json. Set CRED_SOURCE in the instance file to pick a path. Separately, proxy renders a plist whose executable is $MEMORA_PROXY_BIN (default ~/.local/libexec/memora/memora_proxy.py) and whose logs live in $MEMORA_LOG_DIR (default ~/.local/var/log) — nothing creates either on a fresh clone.

    mkdir -p ~/.config/memora ~/.local/libexec/memora ~/.local/var/log
    cp scripts/memora_proxy.py ~/.local/libexec/memora/
    # real values; any key is fine, an absent file is not
    # the default umask is permissive -- chmod 600 keeps other local accounts out
    cat > ~/.config/memora/credentials.mcp.json <<'JSON'
    {"mcpServers":{"memora":{"env":{"CLOUDFLARE_API_TOKEN":"REPLACE","OPENAI_API_KEY":"REPLACE"}}}}
    JSON
    chmod 600 ~/.config/memora/credentials.mcp.json
    

    That JSON is the minimal correct config: both the LLM and embeddings use the default OpenAI host with a real OpenAI key. Do not add OPENAI_BASE_URL pointing at OpenRouter without the embedding pair from Embeddings — OpenRouter has no embeddings endpoint, every embed call 404s, and memora silently falls back to TF-IDF keyword bags while looking healthy.

Then:

./scripts/memora-instance.sh build myinstance   # tags IMAGE from myinstance.env (memora-pilot if IMAGE is unset)
./scripts/memora-instance.sh up      myinstance # runs that same IMAGE
./scripts/memora-instance.sh proxy   myinstance # render the LaunchAgent; run the printed launchctl

up does not publish a host port. The listener the workspace connects to is the proxy. proxy only renders a macOS LaunchAgent and prints the launchctl commands — it does not load the service. Run those printed commands.

The printed workspace URL is always http://127.0.0.1:<PORT>/mcp (the registry default). For a non-default store, append /<name> yourself — a bare /mcp on a registry silently binds MEMORA_DEFAULT_DB:

{"mcpServers": {"memora": {"type": "http", "url": "http://127.0.0.1:<PORT>/mcp/<store>"}}}

Proxy rationale, credentials, instance files, and MEMORA_CONTAINER_BIN: Container Deployment.

<details id="usage"> <summary><big><big><strong>Usage</strong></big></big></summary>

The server runs automatically when configured in Claude Code. Manual invocation:

# Default (stdio mode for MCP)
memora-server

# With graph visualization server
memora-server --graph-port 8765

# HTTP transport (alternative to stdio)
memora-server --transport streamable-http --host 127.0.0.1 --port 8080
</details> <details id="configuration"> <summary><big><big><strong>Configuration</strong></big></big></summary>

Claude Code

Add to .mcp.json in your project root:

Local DB:

{
  "mcpServers": {
    "memora": {
      "command": "memora-server",
      "args": [],
      "env": {
        "MEMORA_DB_PATH": "~/.local/share/memora/memories.db",
        "MEMORA_ALLOW_ANY_TAG": "1",
        "MEMORA_GRAPH_PORT": "8765"
      }
    }
  }
}

Cloud DB (Cloudflare D1) - Recommended:

{
  "mcpServers": {
    "memora": {
      "command": "memora-server",
      "args": ["--no-graph"],
      "env": {
        "MEMORA_STORAGE_URI": "d1://<account-id>/<database-id>",
        "CLOUDFLARE_API_TOKEN": "<your-api-token>",
        "MEMORA_ALLOW_ANY_TAG": "1"
      }
    }
  }
}

With D1, use --no-graph to disable the local visualization server. Instead, use the hosted graph at your Cloudflare Pages URL (see Cloud Graph).

Cloud DB (S3/R2) - Sync mode:

{
  "mcpServers": {
    "memora": {
      "command": "memora-server",
      "args": [],
      "env": {
        "AWS_PROFILE": "memora",
        "AWS_ENDPOINT_URL": "https://<account-id>.r2.cloudflarestorage.com",
        "MEMORA_STORAGE_URI": "s3://memories/memories.db",
        "MEMORA_CLOUD_ENCRYPT": "true",
        "MEMORA_ALLOW_ANY_TAG": "1",
        "MEMORA_GRAPH_PORT": "8765"
      }
    }
  }
}

Codex CLI

Add to ~/.codex/config.toml:

[mcp_servers.memora]
  command = "memora-server"  # or full path: /path/to/bin/memora-server
  args = ["--no-graph"]
  env = {
    AWS_PROFILE = "memora",
    AWS_ENDPOINT_URL = "https://<account-id>.r2.cloudflarestorage.com",
    MEMORA_STORAGE_URI = "s3://memories/memories.db",
    MEMORA_CLOUD_ENCRYPT = "true",
    MEMORA_ALLOW_ANY_TAG = "1",
  }
</details> <details id="environment-variables"> <summary><big><big><strong>Environment Variables</strong></big></big></summary>
VariableDescription
MEMORA_DB_PATHLocal SQLite database path (default: ~/.local/share/memora/memories.db)
MEMORA_STORAGE_URIStorage URI: d1://<account>/<db-id> (D1) or s3://bucket/memories.db (S3/R2). Used when MEMORA_DATABASES is unset.
MEMORA_DATABASESJSON object {name: uri} mapping each store this process serves. Names are one URL path segment (/mcp/<name>): letters, digits, -, _, . only. Duplicate keys, empty values, unsafe names, or non-objects refuse to start rather than silently picking a store. Unset = single-store (legacy). See Multi-database routing.
MEMORA_DEFAULT_DBRegistry name a bare /mcp uses. Required when the registry has more than one database; with exactly one name, that name is the default. A value not in the registry refuses to start.
CLOUDFLARE_API_TOKENAPI token for D1 (d1:// URI). CF_API_TOKEN is accepted as an alias.
MEMORA_CLOUD_ENCRYPTEncrypt the local file before uploading to S3/R2. Unset/false = off; 1/true/yes = on.
MEMORA_CLOUD_COMPRESSCompress the local file before uploading to S3/R2. Unset/false = off; 1/true/yes = on.
MEMORA_CACHE_DIRLocal cache directory for an S3/R2-synced database. Unset: the backend picks a cache path.
MEMORA_ALLOW_ANY_TAGAllow any tag without validation against allowlist (1 to enable)
MEMORA_TAG_FILEPath to a JSON file containing an array of allowed tags, e.g. ["plan", "memora/issues"]
MEMORA_TAGSComma-separated list of allowed tags
MEMORA_HOSTBind address for HTTP transports (default 127.0.0.1). Overridable with --host.
MEMORA_PORTBind port for HTTP transports (default 8000). Overridable with --port.
MEMORA_GRAPH_PORTPort for the knowledge graph visualization server (default: 8765)
MEMORA_TRANSPORTstdio (default), sse, or streamable-http. An unknown env value falls back to stdio; --transport still rejects unknown values. Multi-database routing and the session guard run only on streamable-http.
MEMORA_TOOL_PROFILETool subset exposed to clients: full (default, all 43), leader (19), agent (12). Unset/empty = full; an unknown value refuses to start. See Tool Profiles.
MEMORA_MAX_SESSIONSHard ceiling on concurrent MCP sessions (default 128). 0 disables. A creation rate plus an idle timeout is not a bound — a client that keeps session ids alive can grow without limit at the creation rate. Invalid values refuse to start. Streamable-HTTP only.
MEMORA_MAX_INIT_PER_MINNew sessions admitted per minute (default 120). 0 disables. Invalid values refuse to start. Streamable-HTTP only.
MEMORA_MAX_INIT_BODY_BYTESMaximum initialize request body accepted/buffered (default 65536, minimum 1024). Larger requests receive 413. Invalid values refuse to start. Streamable-HTTP only.
MEMORA_SESSION_IDLE_TIMEOUTSeconds before an abandoned valid session is reaped (default 1800). 0 disables. Invalid values refuse to start. Streamable-HTTP only.
MEMORA_HEALTH_TOKENBearer token for detailed /health/db bodies (names, counts, error text). Unset: only a loopback peer sees detail; everyone else gets aggregate status. FastMCP custom_route() is unauthenticated even when MCP auth is configured. HTTP transports only (memora.health is imported for SSE/streamable-http, not stdio).
MEMORA_HEALTH_TTLSeconds a readiness snapshot may be served before a refresh is due (default 10, cap 3600). Must be > 0. Invalid values refuse to start. HTTP transports only — a malformed value does not abort stdio.
MEMORA_HEALTH_TIMEOUTBound on one refresh pass and on each store probe (default 15, cap 300). Must be > 0. HTTP transports only.
MEMORA_HEALTH_REFRESH_INTERVALHow often the server refreshes readiness on its own (default 15, cap 3600). 0 = poll-only. Without this, a proxy deployment has no loopback caller and the alert surface stays unknown while every database is fine. When periodic refresh is enabled, interval + timeout must be < MEMORA_HEALTH_MAX_STALE. HTTP transports only.
MEMORA_HEALTH_MAX_STALEAge after which a cached per-database result may no longer be reported ready (default 60, cap 3600). Must be >= MEMORA_HEALTH_TTL. HTTP transports only.
MEMORA_STALE_DAYSTwo consumers, two defaults, same name: memory_insights treats an open TODO/issue as stale after 14 days; the graph UI greys closed items after 30 days. Set the variable to override both.
MEMORA_EMBEDDING_MODELEmbedding backend: openai (default), sentence-transformers, or tfidf
SENTENCE_TRANSFORMERS_MODELModel for sentence-transformers (default: all-MiniLM-L6-v2)
MEMORA_EMBEDDING_API_KEYEmbedding provider API key (atomic with base URL — see below)
MEMORA_EMBEDDING_BASE_URLEmbedding provider base URL (atomic with API key — see below)
MEMORA_EMBEDDING_STRICTRecommend 1. Fail hard on embedding errors instead of silent TF-IDF. Without it a broken endpoint keeps answering while every vector becomes a keyword bag (how 756 memories degraded unnoticed).
OPENAI_API_KEYLLM only (dedup/chat) when MEMORA_EMBEDDING_* is set. Embeddings fall back to this key only if both MEMORA_EMBEDDING_API_KEY and MEMORA_EMBEDDING_BASE_URL are unset
OPENAI_BASE_URLLLM base URL (OpenRouter, Azure, etc.). Same atomic fallback rule as the key — not an embeddings URL when you use a split config
OPENAI_EMBEDDING_MODELModel id for the openai embedding backend. Must exist on the embedding host (default text-embedding-3-small is OpenAI-only; Cloudflare needs e.g. @cf/baai/bge-m3)
MEMORA_LLM_ENABLEDEnable LLM-powered deduplication comparison (true/1/yes; default: true)
MEMORA_LLM_MODELModel for deduplication comparison and, if unset, for query rewrite and local chat (default: gpt-4o-mini)
MEMORA_LLM_TIMEOUTSeconds the OpenAI client waits (default 60, floored at 1). A non-numeric value falls back to 60.
MEMORA_REWRITE_MODELModel for RAG query rewriting in the graph chat panel. Unset/empty uses MEMORA_LLM_MODEL.
MEMORA_VECTOR_SCAN_PAGE_SIZERows per page when loading embeddings from D1 (default 1000; non-numeric or <1 falls back to 1000; hard ceiling 10000). At the default, a store under 1000 rows returns the entire corpus plus every embedding in one D1 response, which raced Cloudflare's 30s per-request ceiling and made memory_absorb fail outright. Use 100 on D1 (the instance script already injects that). Paging is a mitigation, not the fix: absorb reads the corpus once per call and reuses a process-local cache keyed on the DB's monotonic embedding_change_epoch.
CHAT_MODELModel for the local graph chat panel. Unset/empty falls back to MEMORA_LLM_MODEL. (The deepseek/deepseek-chat default is Cloudflare Pages wrangler.toml, not this process.)
MEMORA_CLOUD_GRAPH_ENABLEDtrue/1/yes to notify the hosted graph of writes (default off).
MEMORA_CLOUD_GRAPH_WORKER_URLWorker base URL for those broadcasts (POST <url>/broadcast). Unset: broadcasts are skipped.
MEMORA_CLOUD_GRAPH_DEBOUNCESeconds to batch rapid writes before broadcasting (default 1.0).
MEMORA_CLOUD_GRAPH_SYNC_SCRIPTPath captured at startup (default: memora-graph/scripts/sync.sh if that file exists). The current write path does not execute this script — D1 is the source of truth and only the worker broadcast runs.
AWS_PROFILEAWS credentials profile from ~/.aws/credentials (useful for R2)
AWS_ENDPOINT_URLS3-compatible endpoint for R2/MinIO
R2_PUBLIC_DOMAINPublic domain for R2 image URLs
</details> <details id="tool-profiles"> <summary><big><big><strong>Tool Profiles (MEMORA_TOOL_PROFILE)</strong></big></big></summary>

All 43 MCP tools register unconditionally, so every agent session is injected with the full ~12,700-token tool schema even when most tools are never called. MEMORA_TOOL_PROFILE exposes a subset per deployment so a gated tool is genuinely absent — missing from tools/list AND undispatchable (call_tool returns unknown-tool, not a hidden execution). The profile is applied and attested at startup; the active profile and exposed tool count are logged to stderr.

ValueToolsUse
full (default)all 43Direct stdio use; every existing deployment is byte-for-byte unchanged
leader19The agent set plus memory_create_section, memory_store_document, memory_get_document, memory_tags, memory_delete, memory_digest, memory_list
agent12The read/create surface a worker agent needs: memory_absorb, memory_semantic_search, memory_hybrid_search, memory_list_compact, memory_get, memory_related, memory_link, memory_stats, memory_create, memory_create_issue, memory_create_todo, memory_update
  • Unset / empty = full. No existing deployment changes behaviour.
  • An unknown value aborts startup with a message naming the valid values. It never silently falls back to full — a typo must not re-expose destructive maintenance tools (memory_rebuild_embeddings, memory_delete_batch) to every worker. Fail closed.
  • memory_list is in leader but not agent. It was excluded from both while it cost 163-174s on a D1 store against memory_list_compact's 0.22s; #973 fixed that (now ~1.1s). It stays out of agent because a worker's read surface is deliberately narrow, not for speed.
  • The leader/agent boundary is data in memora/tool_profile.py (two frozensets). Editing it is one line, not a sweep of 43 decorators.
  • The prune deletes from FastMCP's private _tool_manager._tools dict, so memora pins mcp>=1.27,<1.28 (the audited minor) and runs a startup attestation through the low-level registered MCP request handlers (_mcp_server.request_handlers[ListToolsRequest] / [CallToolRequest] — the actual dispatch callable real client requests use, not the FastMCP.list_tools / call_tool Python helpers) that refuses to start if the installed SDK routes listing/dispatch elsewhere (private-implementation drift). The pin is the static guard; the attestation is the runtime backstop. Bumping the upper bound requires re-running tests/test_tool_profile.py.
  • Under container deployment the profile is per container while roles are per agent. One container serving a workspace's leader and its workers needs the leader superset; agent would strip create_section/store_document/delete/digest/tags from the leader.
  • memora-server (i.e. memora.server.main()) is the sole supported profiled serving path. A direct embedder that imports memora.server.mcp and calls mcp.run() themselves bypasses profiling entirely (the global mcp still holds all 43 tools); embedders who want profiling must call apply_tool_profile themselves or use main().
# Leader deployment — exposes 19 tools
MEMORA_TOOL_PROFILE=leader memora-server

# Agent worker — exposes 12 tools
MEMORA_TOOL_PROFILE=agent memora-server

# Full (default) — all 43 tools, existing behaviour
memora-server

# Typo refuses to start:
# MEMORA_TOOL_PROFILE=agnt memora-server
# Error: unknown MEMORA_TOOL_PROFILE='agnt'; valid values: full, leader, agent
</details> <details id="multi-database-routing"> <summary><big><big><strong>Multi-database routing</strong></big></big></summary>

One memora process can serve every workspace. MEMORA_DATABASES is a JSON registry of {name: storage URI}; a client reaches its store at /mcp/<name>. The selector is the URL already in .mcp.json, not a tool argument — an optional db on every tool is 43 chances to forget one, and every miss would write into someone else's store.

Unset MEMORA_DATABASES is the old shape: one backend from MEMORA_STORAGE_URI / MEMORA_DB_PATH, one /mcp. Existing stdio deployments do not change.

Routing (streamable-http only):

URLResolves to
/mcp/<name>That registry entry. Unknown names return 404 {"error":"unknown database"} — the body does not list the other names.
/mcpMEMORA_DEFAULT_DB. Required when the registry has more than one database; a single-name registry uses that name.

The binding is sticky per MCP session, not per request. A session opened on /mcp/alpha and reused against /mcp/beta still resolves to alpha. A client cannot half-switch databases mid-conversation.

Malformed configuration refuses to start (it does not fall through to the legacy database): bad JSON, a non-object, duplicate keys, an empty URI, a name that is not one URL path segment, or MEMORA_DEFAULT_DB missing/unknown when more than one database is listed.

Worked pair — run this, connect to this. A streamable-HTTP listener, not an MCP command entry (that would spawn a stdio child that never speaks MCP on stdio). Credentials live on the server process.

MEMORA_DATABASES='{"memora":"d1://<account-id>/<memora-db-id>","ob1":"d1://<account-id>/<ob1-db-id>"}' \
MEMORA_DEFAULT_DB=memora \
CLOUDFLARE_API_TOKEN='<token>' \
MEMORA_VECTOR_SCAN_PAGE_SIZE=100 \
memora-server --transport streamable-http --host 127.0.0.1 --port 8000 --no-graph
{
  "mcpServers": {
    "memora": {
      "type": "http",
      "url": "http://127.0.0.1:8000/mcp/ob1"
    }
  }
}

Container / proxy variant (this host's usual launcher, not the command above): scripts/memora-instance.sh up myinstance starts the same HTTP server inside a container and puts scripts/memora_proxy.py on 127.0.0.1:<PORT> (8910 for the memora instance). The workspace URL is then http://127.0.0.1:8910/mcp/ob1. See Container Deployment.

A registry may mix d1://, s3://, and local paths; parse_backend_uri dispatches on the scheme.

memory_stats reports the bound database. It returns database (the name this session actually resolved) and database_source (path, registry_default, or unconfigured). A valid-but-wrong name in .mcp.json is otherwise undetectable: every tool works, reads succeed, and writes land silently in another project's store. Call memory_stats and check database against the workspace you meant.

Health of a multi-database process: GET /health is liveness (no database I/O — the only signal a supervisor may restart on). GET /health/db is an alert surface (always HTTP 200; status is ok, degraded, unknown — no snapshot yet, a refresh timed out, or evidence older than max staleness — or error if the registry itself is unusable). GET /health/db/{name} is the workspace-specific probe (200 or 503). Withdrawing the whole process because one store is degraded takes the healthy ones down with it.

</details> <details id="container-deployment"> <summary><big><big><strong>Container Deployment</strong></big></big></summary>

With MEMORA_DATABASES unset, a process still binds one database for its lifetime (MEMORA_STORAGE_URI / MEMORA_DB_PATH). That is the original one-store-one-container-one-port shape.

With MEMORA_DATABASES set, one container serves every workspace and clients select a store by URL path (/mcp/<name>). See Multi-database routing. scripts/memora-instance.sh wants one of STORAGE_URI, VOLUME, or MEMORA_DATABASES per instance file (load() requires at least one). If more than one is set, cmd_up uses MEMORA_DATABASES, then STORAGE_URI, then VOLUME.

Dockerfile builds a credential-free image; scripts/memora-instance.sh deploys one instance from instances/myinstance.env (or another named file). The script's runtime CLI is $MEMORA_CONTAINER_BIN (default container — Apple's CLI). Every container operation the script performs honours that override (build, up, status, logs, down). The generated memora_proxy.py process hardcodes container list, which is also why the proxy exists: that runtime reassigns the container's IP on every start.

./scripts/memora-instance.sh build   myinstance   # build the image
./scripts/memora-instance.sh up      myinstance   # run the container
./scripts/memora-instance.sh proxy   myinstance   # render a LaunchAgent + print install commands
./scripts/memora-instance.sh status                # every instance at a glance

Then point the workspace at it — the whole client config, with no secrets in it. A registry instance needs the store in the path (/mcp/<name>); bare /mcp is the registry default:

{"mcpServers": {"memora": {"type": "http", "url": "http://127.0.0.1:8910/mcp/ob1"}}}

Credentials never enter the image, the instance file, or the workspace's HTTP config. They are read at run time from a separate credential config ($CRED_SOURCE — itself a .mcp.json holding only the mcpServers.memora.env block) and injected with -e. If the instance file does not set CRED_SOURCE, the script uses ~/.config/memora/credentials.mcp.json when that file exists, otherwise ~/repos/agentic-box/.mcp.json. Pass through every variable that file defines, not a hand-picked few: a container started with only the embedding keys silently loses memory_absorb's LLM consolidation instead of failing loudly.

Why the proxy exists — read this before deciding you do not need it. The default runtime (Apple's container) reassigns a container's IP on every start, not just on recreate. An MCP client reads its config once at startup, so a moved address does not produce an error: it produces a permanent silent hang. scripts/memora_proxy.py holds a stable 127.0.0.1:<PORT> in front of the moving address and re-resolves per connection.

Two failure modes it distinguishes, which cost an outage to learn:

  • The lookup ran and the container is not listed → it really is gone. Refuse.
  • The lookup could not run (timeout under host memory pressure) → nothing new is known. Keep serving the last known good address, bounded by MEMORA_PROXY_STALE_GRACE (300s). Conflating the two took every workspace offline while the containers were answering normally on unchanged addresses.

Set MEMORA_TOOL_PROFILE per instance (see Tool Profiles). Note the profile is per container while roles are per agent: if one container serves a workspace's leader and its workers, it needs the leader superset.

Deploy-time script variable (not a memora-server env var — it never reaches the process inside the container):

VariableMeaning
MEMORA_CONTAINER_BINCLI every memora-instance.sh container operation uses (build, up, status, logs, down; default container). The generated memora_proxy.py process does not honour this; it hardcodes container list.

instances/README.md covers the config fields and launchd/README.md the supervised proxy. REVERT.md documents restoring a workspace to the direct stdio server.

</details> <details id="semantic-search--embeddings"> <summary><big><big><strong>Semantic Search & Embeddings</strong></big></big></summary>

Memora supports three embedding backends:

BackendInstallQualitySpeed
openai (default)IncludedHigh qualityAPI latency
sentence-transformerspip install memora[local]Good, runs offlineMedium
tfidfIncludedBasic keyword matchingFast

Embeddings and the LLM are configured separately.

RoleVariables
LLM (dedup, chat)OPENAI_API_KEY + OPENAI_BASE_URL
EmbeddingsMEMORA_EMBEDDING_API_KEY + MEMORA_EMBEDDING_BASE_URL (both or neither — atomic pair)
FallbackIf both MEMORA_EMBEDDING_* are unset, embeddings use the full OPENAI_* pair

A partial split (only one MEMORA_EMBEDDING_* set) is rejected so one provider’s secret is never sent to another host.

Trap — OpenRouter has no embeddings endpoint. OpenRouter’s catalogue is chat/multimodal only (no embedding models). Do not point the embedding path at OpenRouter via OPENAI_BASE_URL (or a MEMORA base URL). That combination 404s every embed call; without MEMORA_EMBEDDING_STRICT=1 Memora falls back to TF-IDF and keeps answering, so the store fills with keyword bags while looking healthy. OpenRouter remains fine for the LLM only.

Worked example (LLM via OpenRouter, embeddings via Cloudflare Workers AI):

@cf/baai/bge-m3 is 1024-dimensional. Token needs Workers AI permission. Endpoint shape:

https://api.cloudflare.com/client/v4/accounts/<account_id>/ai/v1

{
  "env": {
    "MEMORA_EMBEDDING_MODEL": "openai",
    "OPENAI_API_KEY": "<openrouter-key>",
    "OPENAI_BASE_URL": "https://openrouter.ai/api/v1",
    "MEMORA_LLM_MODEL": "deepseek/deepseek-chat",
    "MEMORA_EMBEDDING_API_KEY": "<cloudflare-api-token-with-workers-ai>",
    "MEMORA_EMBEDDING_BASE_URL": "https://api.cloudflare.com/client/v4/accounts/<account_id>/ai/v1",
    "OPENAI_EMBEDDING_MODEL": "@cf/baai/bge-m3",
    "MEMORA_EMBEDDING_STRICT": "1"
  }
}

What this fix does (no oversell): embeddings and LLM can use different providers; a partial split is rejected; strict mode turns silent degradation into a hard, named failure.

Automatic: Embeddings and cross-references are computed automatically when you memory_create, memory_update, or memory_create_batch.

Manual rebuild required when the store fingerprint changes — not only MEMORA_EMBEDDING_MODEL, but also:

  • Embedding endpoint (MEMORA_EMBEDDING_BASE_URL / host)
  • Actual model id (OPENAI_EMBEDDING_MODEL, e.g. switching to @cf/baai/bge-m3)
  • Vector kind or dimensions (word-key TF-IDF bags vs dense 1024-d; or 384 vs 1024)
  • Mixed store (some rows dense, some sparse) — cosine similarity only shares keys, so mixed kinds yield 0.0 recall for old rows

Fingerprint form: backend|model|repr (e.g. openai|@cf/baai/bge-m3|dense:1024). Legacy meta value openai alone is treated as a mismatch.

# After changing embedding model/endpoint, rebuild all embeddings
memory_rebuild_embeddings

# Then rebuild cross-references to update the knowledge graph
memory_rebuild_crossrefs
</details> <details id="live-graph-server"> <summary><big><big><strong>Live Graph Server</strong></big></big></summary>

A built-in HTTP server starts automatically with the MCP server, serving an interactive knowledge graph visualization.

<table> <tr> <td align="center"><img src="media/ui_details.png" alt="Details Panel" width="400"><br><em>Details Panel</em></td> <td align="center"><img src="media/ui_timeline.png" alt="Timeline Panel" width="400"><br><em>Timeline Panel</em></td> </tr> </table>

Access locally:

http://localhost:8765/graph

Remote access via SSH:

ssh -L 8765:localhost:8765 user@remote
# Then open http://localhost:8765/graph in your browser

Configuration:

{
  "env": {
    "MEMORA_GRAPH_PORT": "8765"
  }
}

To disable: add "--no-graph" to args in your MCP config.

Graph UI Features

  • Details Panel - View memory content, metadata, tags, and related memories
  • Timeline Panel - Browse memories chronologically, click to highlight in graph
  • History Panel - Action log of all operations with grouped consecutive entries and clickable memory references (deleted memories shown as strikethrough)
  • Chat Panel - Ask questions about your memories using RAG-powered LLM chat with streaming responses and clickable [Memory #ID] references
  • Time Slider - Filter memories by date range, drag to explore history
  • Real-time Updates - Graph, timeline, and history update via SSE when memories change
  • Filters - Tag/section dropdowns, zoom controls
  • Mermaid Rendering - Code blocks render as diagrams

Node Colors

  • 🟣 Tags - Purple shades by tag
  • 🔴 Issues - Red (open), Orange (in progress), Green (resolved), Gray (won't fix)
  • 🔵 TODOs - Blue (open), Orange (in progress), Green (completed), Red (blocked)

Node size reflects connection count.

</details> <details id="cloud-graph"> <summary><big><big><strong>Cloud Graph (Recommended for D1)</strong></big></big></summary>

When using Cloudflare D1 as your database, the graph visualization is hosted on Cloudflare Pages - no local server needed.

Benefits:

  • Access from anywhere (no SSH tunneling)
  • Real-time updates via WebSocket
  • Multi-database support via ?db= parameter
  • Secure access with Cloudflare Zero Trust

Setup:

  1. Create D1 database:

    npx wrangler d1 create memora-graph
    npx wrangler d1 execute memora-graph --file=memora-graph/schema.sql
    
  2. Deploy Pages:

    cd memora-graph
    npx wrangler pages deploy ./public --project-name=memora-graph
    
  3. Configure bindings in Cloudflare Dashboard:

    • Pages → memora-graph → Settings → Bindings
    • Add D1: DB_MEMORA → your database
    • Add R2: R2_MEMORA → your bucket (for images)
  4. Configure MCP with D1 URI:

    {
      "env": {
        "MEMORA_STORAGE_URI": "d1://<account-id>/<database-id>",
        "CLOUDFLARE_API_TOKEN": "<your-token>"
      }
    }
    

Access: https://memora-graph.pages.dev

Secure with Zero Trust:

  1. Cloudflare Dashboard → Zero Trust → Access → Applications
  2. Add application for memora-graph.pages.dev
  3. Create policy with allowed emails
  4. Pages → Settings → Enable Access Policy

See memora-graph/ for detailed setup and multi-database configuration.

</details> <details id="chat-with-memories"> <summary><big><big><strong>Chat with Memories</strong></big></big></summary>

Ask questions about your knowledge base directly from the graph UI. The chat panel uses RAG (Retrieval-Augmented Generation) to search relevant memories and stream LLM responses with tool calling support.

  • Toggle via the floating chat icon at bottom-right
  • Semantic search finds the most relevant memories as context
  • Streaming responses with clickable [Memory #ID] references that focus the graph node
  • Tool calling — the LLM can create, update, and delete memories directly from chat (e.g., "save this as a memory", "delete memory #42", "update memory #10 with...")
  • Works on both the local server and Cloudflare Pages deployment

Configure the chat model:

BackendVariableDefault
Local serverCHAT_MODEL env varFalls back to MEMORA_LLM_MODEL
Cloudflare PagesCHAT_MODEL in wrangler.tomldeepseek/deepseek-chat

Requires an OpenAI-compatible API (OPENAI_API_KEY + OPENAI_BASE_URL for local, OPENROUTER_API_KEY secret for Cloudflare). The chat model must support tool use (function calling).

</details> <details id="llm-deduplication"> <summary><big><big><strong>LLM Deduplication</strong></big></big></summary>

Find and merge duplicate memories using AI-powered semantic comparison:

# Find potential duplicates (uses cross-refs + optional LLM analysis)
memory_find_duplicates(min_similarity=0.7, max_similarity=0.95, limit=10, use_llm=True)

# Merge duplicates (append, prepend, or replace strategies)
memory_merge(source_id=123, target_id=456, merge_strategy="append")

LLM Comparison analyzes memory pairs and returns:

  • verdict: "duplicate", "similar", or "different"
  • confidence: 0.0-1.0 score
  • reasoning: Brief explanation
  • suggested_action: "merge", "keep_both", or "review"

Works with any OpenAI-compatible chat API (OpenAI, OpenRouter, Azure, etc.) via OPENAI_BASE_URL. OpenRouter is fine for this LLM path; it does not provide embeddings — configure embeddings separately (see Semantic Search & Embeddings).

</details> <details id="document-storage"> <summary><big><big><strong>Document Storage</strong></big></big></summary>

Store structured documents (research reports, architecture decisions, post-mortems) as searchable fragment trees:

# Store a markdown document — auto-parsed into typed fragments
memory_store_document(
    content="# Research Report\n\n## Evidence Table\n| Claim | Confidence |\n...",
    document_key="research/memora-enhancements-2026-04-08",
    tags=["memora/research"]
)
# Returns: {root_id: 230, fragment_count: 100, node_map: {claim: [...], plan_item: [...], ...}}

# Retrieve the full document or specific fragment types
memory_get_document(document_key="research/memora-enhancements-2026-04-08")
memory_get_document(document_key="...", node_kinds=["claim"], content_mode="full")

# Delete a document and all its fragments
memory_delete_document(document_key="research/memora-enhancements-2026-04-08")

How it works: The parser splits markdown by structure — tables become individual claims, numbered lists become plan items, URL lists become references, and risk sections become risk fragments. Each fragment is independently searchable via memory_semantic_search while the full document is retrievable as a unit.

Fragment types: claim, plan_item, reference, section_chunk, risk

Integrity guards: Document fragments are protected from accidental modification:

  • memory_delete requires force=True for fragments
  • memory_merge refuses to merge fragments
  • memory_absorb excludes fragments from similarity matching
  • memory_find_duplicates and memory_detect_supersessions skip fragments
  • Graph UI hides fragments, shows only the document root node
</details> <details id="memory-automation-tools"> <summary><big><big><strong>Memory Automation Tools</strong></big></big></summary>

Structured tools for common memory types:

# Create a TODO with status and priority
memory_create_todo(content="Implement feature X", status="open", priority="high", category="backend")

# Create an issue with severity
memory_create_issue(content="Bug in login flow", status="open", severity="major", component="auth")

# Create a section placeholder (hidden from graph)
memory_create_section(content="Architecture", section="docs", subsection="api")
</details> <details id="memory-insights"> <summary><big><big><strong>Memory Insights</strong></big></big></summary>

Analyze stored memories and surface actionable insights:

# Full analysis with LLM-powered pattern detection
memory_insights(period="7d", include_llm_analysis=True)

# Quick summary without LLM (faster, no API key needed)
memory_insights(period="1m", include_llm_analysis=False)

Returns:

  • Activity summary — memories created in the period, grouped by type and tag
  • Open items — open TODOs and issues with stale detection (configurable via MEMORA_STALE_DAYS; memory_insights default 14, graph UI default 30 — same variable, two consumers)
  • Consolidation candidates — similar memory pairs that could be merged
  • LLM analysis — themes, focus areas, knowledge gaps, and a summary (requires OPENAI_API_KEY)
</details> <details id="memory-linking"> <summary><big><big><strong>Memory Linking</strong></big></big></summary>

Manage relationships between memories:

# Create typed edges between memories
memory_link(from_id=1, to_id=2, edge_type="implements", bidirectional=True)

# Edge types: references, implements, supersedes, extends, contradicts, related_to

# Remove links
memory_unlink(from_id=1, to_id=2)

# Boost memory importance for ranking
memory_boost(memory_id=42, boost_amount=0.5)

# Detect clusters of related memories
memory_clusters(min_cluster_size=2, min_score=0.3)
</details> <details id="knowledge-graph-export"> <summary><big><big><strong>Knowledge Graph Export (Optional)</strong></big></big></summary>

For offline viewing, export memories as a static HTML file:

memory_export_graph(output_path="~/memories_graph.html", min_score=0.25)

This is optional - the Live Graph Server provides the same visualization with real-time updates.

</details> <details id="neovim-integration"> <summary><big><big><strong>Neovim Integration</strong></big></big></summary>

Browse memories directly in Neovim with Telescope. Copy the plugin to your config:

# For kickstart.nvim / lazy.nvim
cp nvim/memora.lua ~/.config/nvim/lua/kickstart/plugins/

Usage: Press <leader>sm to open the memory browser with fuzzy search and preview.

Requires: telescope.nvim, plenary.nvim, and memora installed in your Python environment.

</details>
Теги
PythonCloudLocal