ai.sovantica/engrava
Tamper-evident audit, MindQL, and Action Records — embedded memory database for AI agents.
Open source Open in the app JSON README (API)
About
Tamper-evident audit, MindQL, and Action Records — embedded memory database for AI agents.
Details
- Kind
- MCP servers
- Topic
- AI, RAG & memory
- Publisher
- ai.sovantica
- Origin
- official
- Category
- ferramentas
- Transport
- local
- Version
- 0.6.0
- Stars
- 1
- Open pull requests
- 1
- Last push
- 2026-08-24T16:08:37Z
- Repository state
- ativo
- Language
- Python
- License
- MIT
- Added
- 2026-08-29 03:00:41
- Updated
- 2026-08-29 03:00:41
- Origin id
ai.sovantica/engrava
README
<!-- mcp-name: ai.sovantica/engrava -->
# Engrava MCP
[](https://github.com/sovantica/engrava-mcp/actions/workflows/ci.yml)
[](https://pypi.org/project/engrava-mcp/)
[](https://pypi.org/project/engrava-mcp/)
[](https://opensource.org/licenses/MIT)
**The [Model Context Protocol](https://modelcontextprotocol.io) server for
[Engrava](https://github.com/sovantica/engrava)** — expose an agent memory
database to any MCP client (Claude Desktop, Claude Code, Cursor, Windsurf,
VS Code, …) over stdio.
`engrava-mcp` is a standalone, runnable package that consumes Engrava's public
API. It is the one way to run Engrava as a memory server; the `engrava` library
itself ships no MCP code.
```bash
uvx engrava-mcp # run the server (no install step)
# or
pip install engrava-mcp
engrava-mcp # spawned by your MCP client over stdio
```
Installing `engrava-mcp` pulls in `engrava` transitively, so you also get the
`import engrava` library in the same environment.
## Compatibility
`engrava-mcp` follows Engrava's version: **`engrava-mcp X.Y.z` targets `engrava X.Y`**
and requires `engrava >=X.Y,<X.(Y+1)`. This is a one-way version mirror for legibility —
**not** a lockstep: Engrava releases on its own cadence, and `engrava-mcp` patch releases
are independent.
| engrava-mcp | Works with engrava |
|---|---|
| `0.5.x` | `>=0.5,<0.6` |
| `0.6.x` | `>=0.6,<0.7` |
The dependency range is the source of truth. Normal installs resolve a compatible
`engrava` automatically; if you pin `engrava` yourself, keep it within that range. If no
matching `engrava-mcp` exists yet for a newer `engrava` (e.g. a fresh `engrava 0.7`), that
pairing is **not yet verified/supported** — not broken; stay on a supported pair until a
matching `engrava-mcp` ships.
## Which package do I want?
| Goal | Install |
|---|---|
| Build on the Engrava Python API (memory DB in your own code) | `pip install engrava` |
| Run Engrava as a memory server for an MCP client | `uvx engrava-mcp` (or `pip install engrava-mcp`) |
There is no third option.
## Migrating from `engrava[mcp]`
The server used to ship inside Engrava as the `engrava[mcp]` extra and an
in-`engrava` `engrava-mcp` command. As of Engrava 0.5.0 it lives here instead.
| Before | After |
|---|---|
| `pip install "engrava[mcp]"` | `pip install engrava-mcp` (or `uvx engrava-mcp`) |
| `engrava-mcp` (installed by engrava) | `engrava-mcp` (installed by this package) |
| client `mcp.json`: `"command": "engrava-mcp"` | client `mcp.json`: `"command": "uvx", "args": ["engrava-mcp"]` |
- **Watch out:** `pip install "engrava[mcp]"` against Engrava 0.5 **does not
fail** — pip ignores the now-unknown extra and quietly installs bare
`engrava`, so it can look like the server installed when it did not. Install
`engrava-mcp` instead.
- Update any pinned requirement strings (`engrava[mcp]>=...`) to depend on
`engrava-mcp`, not just reinstall.
- **Your store configuration is unchanged** — the same `engrava.yaml` / env vars
work exactly as before (see [Configuration](#configuration)).
## Configuration
The server resolves its store from environment variables, in priority order:
| Variable | Meaning |
|---|---|
| `ENGRAVA_MCP_CONFIG` | Path to an `engrava.yaml`. Built with the full configuration — embedding provider, vector backend, journal, TTL. **Recommended.** |
| `ENGRAVA_DB_PATH` | Path to a bare SQLite database file. Zero-config quick-start; no embedding provider is configured, so semantic (vector) search is inert — full-text search, the graph, MindQL, and the audit trail still work. "Zero-config" means Engrava's default search policy, so `search_memory`'s `recency_now` is honoured on this route too — recency is scored against the timestamp you supply, under Engrava's default search weights. |
| `ENGRAVA_MCP_READ_ONLY` | When set to `1` / `true` / `yes`, the write tools are not registered, so the server exposes a read-only surface. |
**Recommended:** give the MCP server the same `engrava.yaml` your application
uses. The `yaml` is the only place to declare an embedding provider (and its
model / key), which the server needs to embed a *new query* at search time for
semantic search. With only `ENGRAVA_DB_PATH` set, the server emits a startup
warning that semantic search is inert and points you at `ENGRAVA_MCP_CONFIG`.
### Store-hook extensions need the config path
Engrava extensions that hook the store — anything wired through an
`engrava.yaml`'s `hooks:` section — are attached only on the
`ENGRAVA_MCP_CONFIG` launch. `ENGRAVA_DB_PATH` opens a bare database and carries
no configuration, so it runs with Engrava's default hooks and cannot attach a
store-hook extension. That is deliberate: it is an intentionally minimal
read/write facade.
Installing such an extension and starting with `ENGRAVA_DB_PATH` therefore
leaves its store hooks unattached in this server. When an installed package
advertises any extension, the server emits a startup warning naming it — it
reports what is advertised, not what each one does, since it never loads them
itself — so you can tell the difference between "nothing advertised" and
"advertised but nothing wired it here". If reading the installed-package
metadata raises an ordinary error, the server attempts to log that instead and
carries on starting. Both go through Python's `logging`, so whether and where
they surface is up to your logging configuration. To wire a store hook, launch
with `ENGRAVA_MCP_CONFIG` pointing at an `engrava.yaml` with a `hooks:`
section:
```yaml
hooks:
class: "my_package.hooks.MyHooks"
```
### Example `engrava.yaml`
```yaml
db_path: ./memory.db
embeddings:
provider: openai # or: ollama, sentence-transformer, huggingface
model: text-embedding-3-small
api_key: ${OPENAI_API_KEY}
```
## Client setup
Point your MCP client at the server over stdio. For example, a typical
`mcp.json` entry:
```json
{
"mcpServers": {
"engrava": {
"command": "uvx",
"args": ["engrava-mcp"],
"env": {
"ENGRAVA_MCP_CONFIG": "/absolute/path/to/engrava.yaml"
}
}
}
}
```
Use `ENGRAVA_DB_PATH` instead of `ENGRAVA_MCP_CONFIG` for the zero-config
quick-start, and add `"ENGRAVA_MCP_READ_ONLY": "1"` for an app-writes /
agent-reads deployment.
### Running without uvx
```bash
engrava-mcp # console script
python -m engrava_mcp # module run
python -m engrava_mcp.server # module run (server module directly)
```
## Optional providers
The default install supports the vector backend and HTTP-based embedding
providers (OpenAI / Ollama) once configured in the `yaml`. Heavier providers are
opt-in extras that mirror Engrava's own extras:
```bash
uvx --from "engrava-mcp[local]" engrava-mcp # sentence-transformers (local model)
uvx --from "engrava-mcp[hf]" engrava-mcp # HuggingFace Inference API
uvx --from "engrava-mcp[openai]" engrava-mcp # OpenAI-compatible embeddings deps
uvx --from "engrava-mcp[ollama]" engrava-mcp # Ollama embeddings deps
```
## The surface
- **Tools (13):** `get_thought`, `search_memory`, `search_keywords`,
`list_memory`, `query_memory`, `memory_stats`, `get_edges`, `list_edges`
(read); `store_thought`, `update_thought`, `link_thoughts`, `delete_thought`,
`delete_edge` (write, gated by `ENGRAVA_MCP_READ_ONLY`).
- **Resources (3):** `engrava://thought/{thought_id}`, `engrava://stats`,
`engrava://recent`.
- **Prompts (3):** `summarize_recent_memory`, `find_related`, `reflect_on_topic`.
`query_memory` accepts only MindQL `FIND` queries; raw SQL and every other
command are rejected.
`get_edges` traverses a thought's edges by direction (`IN` / `OUT` / `BOTH`);
`list_edges` browses edges filtered by type, source, or metadata.
`link_thoughts` accepts optional edge `metadata` (JSON fields that `list_edges`
can filter on). `search_memory` accepts an optional `recency_now` (ISO-8601
timestamp) giving the moment to measure age against (transaction time); recency
takes part in the ranking only when you pass it.
## Development
```bash
pip install -e ".[dev]"
ruff check src/ tests/
ruff format --check src/ tests/
mypy --strict src/
pytest --cov --cov-fail-under=90
```
## License
MIT