engrava-mcp 0.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- engrava_mcp-0.5.0/LICENSE +21 -0
- engrava_mcp-0.5.0/PKG-INFO +206 -0
- engrava_mcp-0.5.0/README.md +163 -0
- engrava_mcp-0.5.0/pyproject.toml +112 -0
- engrava_mcp-0.5.0/setup.cfg +4 -0
- engrava_mcp-0.5.0/src/engrava_mcp/__init__.py +28 -0
- engrava_mcp-0.5.0/src/engrava_mcp/__main__.py +12 -0
- engrava_mcp-0.5.0/src/engrava_mcp/_compat.py +125 -0
- engrava_mcp-0.5.0/src/engrava_mcp/config.py +165 -0
- engrava_mcp-0.5.0/src/engrava_mcp/py.typed +0 -0
- engrava_mcp-0.5.0/src/engrava_mcp/server.py +1535 -0
- engrava_mcp-0.5.0/src/engrava_mcp.egg-info/PKG-INFO +206 -0
- engrava_mcp-0.5.0/src/engrava_mcp.egg-info/SOURCES.txt +28 -0
- engrava_mcp-0.5.0/src/engrava_mcp.egg-info/dependency_links.txt +1 -0
- engrava_mcp-0.5.0/src/engrava_mcp.egg-info/entry_points.txt +2 -0
- engrava_mcp-0.5.0/src/engrava_mcp.egg-info/requires.txt +23 -0
- engrava_mcp-0.5.0/src/engrava_mcp.egg-info/top_level.txt +1 -0
- engrava_mcp-0.5.0/tests/test_compat.py +92 -0
- engrava_mcp-0.5.0/tests/test_config.py +83 -0
- engrava_mcp-0.5.0/tests/test_delete_tools.py +96 -0
- engrava_mcp-0.5.0/tests/test_errors.py +550 -0
- engrava_mcp-0.5.0/tests/test_gating.py +88 -0
- engrava_mcp-0.5.0/tests/test_main.py +60 -0
- engrava_mcp-0.5.0/tests/test_prompts.py +303 -0
- engrava_mcp-0.5.0/tests/test_read_tools.py +394 -0
- engrava_mcp-0.5.0/tests/test_resources.py +272 -0
- engrava_mcp-0.5.0/tests/test_server.py +503 -0
- engrava_mcp-0.5.0/tests/test_stdio_smoke.py +93 -0
- engrava_mcp-0.5.0/tests/test_surface_parity.py +159 -0
- engrava_mcp-0.5.0/tests/test_write_tools.py +219 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sovantica
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: engrava-mcp
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: Model Context Protocol server for Engrava — expose an agent memory database to any MCP client over stdio.
|
|
5
|
+
Author-email: Sovantica <hello@sovantica.ai>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://engrava.ai
|
|
8
|
+
Project-URL: Documentation, https://github.com/sovantica/engrava-mcp
|
|
9
|
+
Project-URL: Repository, https://github.com/sovantica/engrava-mcp
|
|
10
|
+
Project-URL: Issues, https://github.com/sovantica/engrava-mcp/issues
|
|
11
|
+
Keywords: mcp,model-context-protocol,ai-agents,agent-memory,graph-memory,engrava,llm,stdio-server
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: engrava<0.6,>=0.5
|
|
25
|
+
Requires-Dist: mcp<2,>=1.27
|
|
26
|
+
Requires-Dist: engrava[vec]<0.6,>=0.5
|
|
27
|
+
Requires-Dist: httpx>=0.27.0
|
|
28
|
+
Provides-Extra: openai
|
|
29
|
+
Requires-Dist: engrava[embeddings-openai]<0.6,>=0.5; extra == "openai"
|
|
30
|
+
Provides-Extra: ollama
|
|
31
|
+
Requires-Dist: engrava[embeddings-ollama]<0.6,>=0.5; extra == "ollama"
|
|
32
|
+
Provides-Extra: hf
|
|
33
|
+
Requires-Dist: engrava[embeddings-hf]<0.6,>=0.5; extra == "hf"
|
|
34
|
+
Provides-Extra: local
|
|
35
|
+
Requires-Dist: engrava[embeddings-local]<0.6,>=0.5; extra == "local"
|
|
36
|
+
Provides-Extra: dev
|
|
37
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
38
|
+
Requires-Dist: pytest-asyncio>=0.24.0; extra == "dev"
|
|
39
|
+
Requires-Dist: pytest-cov>=5.0.0; extra == "dev"
|
|
40
|
+
Requires-Dist: mypy>=1.11.0; extra == "dev"
|
|
41
|
+
Requires-Dist: ruff>=0.7.0; extra == "dev"
|
|
42
|
+
Dynamic: license-file
|
|
43
|
+
|
|
44
|
+
# Engrava MCP
|
|
45
|
+
|
|
46
|
+
**The [Model Context Protocol](https://modelcontextprotocol.io) server for
|
|
47
|
+
[Engrava](https://github.com/sovantica/engrava)** — expose an agent memory
|
|
48
|
+
database to any MCP client (Claude Desktop, Claude Code, Cursor, Windsurf,
|
|
49
|
+
VS Code, …) over stdio.
|
|
50
|
+
|
|
51
|
+
`engrava-mcp` is a standalone, runnable package that consumes Engrava's public
|
|
52
|
+
API. It is the one way to run Engrava as a memory server; the `engrava` library
|
|
53
|
+
itself ships no MCP code.
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
uvx engrava-mcp # run the server (no install step)
|
|
57
|
+
# or
|
|
58
|
+
pip install engrava-mcp
|
|
59
|
+
engrava-mcp # spawned by your MCP client over stdio
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Installing `engrava-mcp` pulls in `engrava` transitively, so you also get the
|
|
63
|
+
`import engrava` library in the same environment.
|
|
64
|
+
|
|
65
|
+
## Compatibility
|
|
66
|
+
|
|
67
|
+
`engrava-mcp` follows Engrava's version: **`engrava-mcp X.Y.z` targets `engrava X.Y`**
|
|
68
|
+
and requires `engrava >=X.Y,<X.(Y+1)`. This is a one-way version mirror for legibility —
|
|
69
|
+
**not** a lockstep: Engrava releases on its own cadence, and `engrava-mcp` patch releases
|
|
70
|
+
are independent.
|
|
71
|
+
|
|
72
|
+
| engrava-mcp | Works with engrava |
|
|
73
|
+
|---|---|
|
|
74
|
+
| `0.5.x` | `>=0.5,<0.6` |
|
|
75
|
+
|
|
76
|
+
The dependency range is the source of truth. Normal installs resolve a compatible
|
|
77
|
+
`engrava` automatically; if you pin `engrava` yourself, keep it within that range. If no
|
|
78
|
+
matching `engrava-mcp` exists yet for a newer `engrava` (e.g. a fresh `engrava 0.6`), that
|
|
79
|
+
pairing is **not yet verified/supported** — not broken; stay on a supported pair until a
|
|
80
|
+
matching `engrava-mcp` ships.
|
|
81
|
+
|
|
82
|
+
## Which package do I want?
|
|
83
|
+
|
|
84
|
+
| Goal | Install |
|
|
85
|
+
|---|---|
|
|
86
|
+
| Build on the Engrava Python API (memory DB in your own code) | `pip install engrava` |
|
|
87
|
+
| Run Engrava as a memory server for an MCP client | `uvx engrava-mcp` (or `pip install engrava-mcp`) |
|
|
88
|
+
|
|
89
|
+
There is no third option.
|
|
90
|
+
|
|
91
|
+
## Migrating from `engrava[mcp]`
|
|
92
|
+
|
|
93
|
+
The server used to ship inside Engrava as the `engrava[mcp]` extra and an
|
|
94
|
+
in-`engrava` `engrava-mcp` command. As of Engrava 0.5.0 it lives here instead.
|
|
95
|
+
|
|
96
|
+
| Before | After |
|
|
97
|
+
|---|---|
|
|
98
|
+
| `pip install "engrava[mcp]"` | `pip install engrava-mcp` (or `uvx engrava-mcp`) |
|
|
99
|
+
| `engrava-mcp` (installed by engrava) | `engrava-mcp` (installed by this package) |
|
|
100
|
+
| client `mcp.json`: `"command": "engrava-mcp"` | client `mcp.json`: `"command": "uvx", "args": ["engrava-mcp"]` |
|
|
101
|
+
|
|
102
|
+
- **Watch out:** `pip install "engrava[mcp]"` against Engrava 0.5 **does not
|
|
103
|
+
fail** — pip ignores the now-unknown extra and quietly installs bare
|
|
104
|
+
`engrava`, so it can look like the server installed when it did not. Install
|
|
105
|
+
`engrava-mcp` instead.
|
|
106
|
+
- Update any pinned requirement strings (`engrava[mcp]>=...`) to depend on
|
|
107
|
+
`engrava-mcp`, not just reinstall.
|
|
108
|
+
- **Your store configuration is unchanged** — the same `engrava.yaml` / env vars
|
|
109
|
+
work exactly as before (see [Configuration](#configuration)).
|
|
110
|
+
|
|
111
|
+
## Configuration
|
|
112
|
+
|
|
113
|
+
The server resolves its store from environment variables, in priority order:
|
|
114
|
+
|
|
115
|
+
| Variable | Meaning |
|
|
116
|
+
|---|---|
|
|
117
|
+
| `ENGRAVA_MCP_CONFIG` | Path to an `engrava.yaml`. Built with the full configuration — embedding provider, vector backend, journal, TTL. **Recommended.** |
|
|
118
|
+
| `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. |
|
|
119
|
+
| `ENGRAVA_MCP_READ_ONLY` | When set to `1` / `true` / `yes`, the write tools are not registered, so the server exposes a read-only surface. |
|
|
120
|
+
|
|
121
|
+
**Recommended:** give the MCP server the same `engrava.yaml` your application
|
|
122
|
+
uses. The `yaml` is the only place to declare an embedding provider (and its
|
|
123
|
+
model / key), which the server needs to embed a *new query* at search time for
|
|
124
|
+
semantic search. With only `ENGRAVA_DB_PATH` set, the server logs a startup
|
|
125
|
+
warning that semantic search is inert and points you at `ENGRAVA_MCP_CONFIG`.
|
|
126
|
+
|
|
127
|
+
### Example `engrava.yaml`
|
|
128
|
+
|
|
129
|
+
```yaml
|
|
130
|
+
db_path: ./memory.db
|
|
131
|
+
embeddings:
|
|
132
|
+
provider: openai # or: ollama, sentence-transformer, huggingface
|
|
133
|
+
model: text-embedding-3-small
|
|
134
|
+
api_key: ${OPENAI_API_KEY}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Client setup
|
|
138
|
+
|
|
139
|
+
Point your MCP client at the server over stdio. For example, a typical
|
|
140
|
+
`mcp.json` entry:
|
|
141
|
+
|
|
142
|
+
```json
|
|
143
|
+
{
|
|
144
|
+
"mcpServers": {
|
|
145
|
+
"engrava": {
|
|
146
|
+
"command": "uvx",
|
|
147
|
+
"args": ["engrava-mcp"],
|
|
148
|
+
"env": {
|
|
149
|
+
"ENGRAVA_MCP_CONFIG": "/absolute/path/to/engrava.yaml"
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Use `ENGRAVA_DB_PATH` instead of `ENGRAVA_MCP_CONFIG` for the zero-config
|
|
157
|
+
quick-start, and add `"ENGRAVA_MCP_READ_ONLY": "1"` for an app-writes /
|
|
158
|
+
agent-reads deployment.
|
|
159
|
+
|
|
160
|
+
### Running without uvx
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
engrava-mcp # console script
|
|
164
|
+
python -m engrava_mcp # module run
|
|
165
|
+
python -m engrava_mcp.server # module run (server module directly)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Optional providers
|
|
169
|
+
|
|
170
|
+
The default install supports the vector backend and HTTP-based embedding
|
|
171
|
+
providers (OpenAI / Ollama) once configured in the `yaml`. Heavier providers are
|
|
172
|
+
opt-in extras that mirror Engrava's own extras:
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
uvx --from "engrava-mcp[local]" engrava-mcp # sentence-transformers (local model)
|
|
176
|
+
uvx --from "engrava-mcp[hf]" engrava-mcp # HuggingFace Inference API
|
|
177
|
+
uvx --from "engrava-mcp[openai]" engrava-mcp # OpenAI-compatible embeddings deps
|
|
178
|
+
uvx --from "engrava-mcp[ollama]" engrava-mcp # Ollama embeddings deps
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## The surface
|
|
182
|
+
|
|
183
|
+
- **Tools (11):** `get_thought`, `search_memory`, `search_keywords`,
|
|
184
|
+
`list_memory`, `query_memory`, `memory_stats` (read); `store_thought`,
|
|
185
|
+
`update_thought`, `link_thoughts`, `delete_thought`, `delete_edge` (write,
|
|
186
|
+
gated by `ENGRAVA_MCP_READ_ONLY`).
|
|
187
|
+
- **Resources (3):** `engrava://thought/{thought_id}`, `engrava://stats`,
|
|
188
|
+
`engrava://recent`.
|
|
189
|
+
- **Prompts (3):** `summarize_recent_memory`, `find_related`, `reflect_on_topic`.
|
|
190
|
+
|
|
191
|
+
`query_memory` accepts only MindQL `FIND` queries; raw SQL and every other
|
|
192
|
+
command are rejected.
|
|
193
|
+
|
|
194
|
+
## Development
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
pip install -e ".[dev]"
|
|
198
|
+
ruff check src/ tests/
|
|
199
|
+
ruff format --check src/ tests/
|
|
200
|
+
mypy --strict src/
|
|
201
|
+
pytest --cov --cov-fail-under=90
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## License
|
|
205
|
+
|
|
206
|
+
MIT
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# Engrava MCP
|
|
2
|
+
|
|
3
|
+
**The [Model Context Protocol](https://modelcontextprotocol.io) server for
|
|
4
|
+
[Engrava](https://github.com/sovantica/engrava)** — expose an agent memory
|
|
5
|
+
database to any MCP client (Claude Desktop, Claude Code, Cursor, Windsurf,
|
|
6
|
+
VS Code, …) over stdio.
|
|
7
|
+
|
|
8
|
+
`engrava-mcp` is a standalone, runnable package that consumes Engrava's public
|
|
9
|
+
API. It is the one way to run Engrava as a memory server; the `engrava` library
|
|
10
|
+
itself ships no MCP code.
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
uvx engrava-mcp # run the server (no install step)
|
|
14
|
+
# or
|
|
15
|
+
pip install engrava-mcp
|
|
16
|
+
engrava-mcp # spawned by your MCP client over stdio
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Installing `engrava-mcp` pulls in `engrava` transitively, so you also get the
|
|
20
|
+
`import engrava` library in the same environment.
|
|
21
|
+
|
|
22
|
+
## Compatibility
|
|
23
|
+
|
|
24
|
+
`engrava-mcp` follows Engrava's version: **`engrava-mcp X.Y.z` targets `engrava X.Y`**
|
|
25
|
+
and requires `engrava >=X.Y,<X.(Y+1)`. This is a one-way version mirror for legibility —
|
|
26
|
+
**not** a lockstep: Engrava releases on its own cadence, and `engrava-mcp` patch releases
|
|
27
|
+
are independent.
|
|
28
|
+
|
|
29
|
+
| engrava-mcp | Works with engrava |
|
|
30
|
+
|---|---|
|
|
31
|
+
| `0.5.x` | `>=0.5,<0.6` |
|
|
32
|
+
|
|
33
|
+
The dependency range is the source of truth. Normal installs resolve a compatible
|
|
34
|
+
`engrava` automatically; if you pin `engrava` yourself, keep it within that range. If no
|
|
35
|
+
matching `engrava-mcp` exists yet for a newer `engrava` (e.g. a fresh `engrava 0.6`), that
|
|
36
|
+
pairing is **not yet verified/supported** — not broken; stay on a supported pair until a
|
|
37
|
+
matching `engrava-mcp` ships.
|
|
38
|
+
|
|
39
|
+
## Which package do I want?
|
|
40
|
+
|
|
41
|
+
| Goal | Install |
|
|
42
|
+
|---|---|
|
|
43
|
+
| Build on the Engrava Python API (memory DB in your own code) | `pip install engrava` |
|
|
44
|
+
| Run Engrava as a memory server for an MCP client | `uvx engrava-mcp` (or `pip install engrava-mcp`) |
|
|
45
|
+
|
|
46
|
+
There is no third option.
|
|
47
|
+
|
|
48
|
+
## Migrating from `engrava[mcp]`
|
|
49
|
+
|
|
50
|
+
The server used to ship inside Engrava as the `engrava[mcp]` extra and an
|
|
51
|
+
in-`engrava` `engrava-mcp` command. As of Engrava 0.5.0 it lives here instead.
|
|
52
|
+
|
|
53
|
+
| Before | After |
|
|
54
|
+
|---|---|
|
|
55
|
+
| `pip install "engrava[mcp]"` | `pip install engrava-mcp` (or `uvx engrava-mcp`) |
|
|
56
|
+
| `engrava-mcp` (installed by engrava) | `engrava-mcp` (installed by this package) |
|
|
57
|
+
| client `mcp.json`: `"command": "engrava-mcp"` | client `mcp.json`: `"command": "uvx", "args": ["engrava-mcp"]` |
|
|
58
|
+
|
|
59
|
+
- **Watch out:** `pip install "engrava[mcp]"` against Engrava 0.5 **does not
|
|
60
|
+
fail** — pip ignores the now-unknown extra and quietly installs bare
|
|
61
|
+
`engrava`, so it can look like the server installed when it did not. Install
|
|
62
|
+
`engrava-mcp` instead.
|
|
63
|
+
- Update any pinned requirement strings (`engrava[mcp]>=...`) to depend on
|
|
64
|
+
`engrava-mcp`, not just reinstall.
|
|
65
|
+
- **Your store configuration is unchanged** — the same `engrava.yaml` / env vars
|
|
66
|
+
work exactly as before (see [Configuration](#configuration)).
|
|
67
|
+
|
|
68
|
+
## Configuration
|
|
69
|
+
|
|
70
|
+
The server resolves its store from environment variables, in priority order:
|
|
71
|
+
|
|
72
|
+
| Variable | Meaning |
|
|
73
|
+
|---|---|
|
|
74
|
+
| `ENGRAVA_MCP_CONFIG` | Path to an `engrava.yaml`. Built with the full configuration — embedding provider, vector backend, journal, TTL. **Recommended.** |
|
|
75
|
+
| `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. |
|
|
76
|
+
| `ENGRAVA_MCP_READ_ONLY` | When set to `1` / `true` / `yes`, the write tools are not registered, so the server exposes a read-only surface. |
|
|
77
|
+
|
|
78
|
+
**Recommended:** give the MCP server the same `engrava.yaml` your application
|
|
79
|
+
uses. The `yaml` is the only place to declare an embedding provider (and its
|
|
80
|
+
model / key), which the server needs to embed a *new query* at search time for
|
|
81
|
+
semantic search. With only `ENGRAVA_DB_PATH` set, the server logs a startup
|
|
82
|
+
warning that semantic search is inert and points you at `ENGRAVA_MCP_CONFIG`.
|
|
83
|
+
|
|
84
|
+
### Example `engrava.yaml`
|
|
85
|
+
|
|
86
|
+
```yaml
|
|
87
|
+
db_path: ./memory.db
|
|
88
|
+
embeddings:
|
|
89
|
+
provider: openai # or: ollama, sentence-transformer, huggingface
|
|
90
|
+
model: text-embedding-3-small
|
|
91
|
+
api_key: ${OPENAI_API_KEY}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Client setup
|
|
95
|
+
|
|
96
|
+
Point your MCP client at the server over stdio. For example, a typical
|
|
97
|
+
`mcp.json` entry:
|
|
98
|
+
|
|
99
|
+
```json
|
|
100
|
+
{
|
|
101
|
+
"mcpServers": {
|
|
102
|
+
"engrava": {
|
|
103
|
+
"command": "uvx",
|
|
104
|
+
"args": ["engrava-mcp"],
|
|
105
|
+
"env": {
|
|
106
|
+
"ENGRAVA_MCP_CONFIG": "/absolute/path/to/engrava.yaml"
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Use `ENGRAVA_DB_PATH` instead of `ENGRAVA_MCP_CONFIG` for the zero-config
|
|
114
|
+
quick-start, and add `"ENGRAVA_MCP_READ_ONLY": "1"` for an app-writes /
|
|
115
|
+
agent-reads deployment.
|
|
116
|
+
|
|
117
|
+
### Running without uvx
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
engrava-mcp # console script
|
|
121
|
+
python -m engrava_mcp # module run
|
|
122
|
+
python -m engrava_mcp.server # module run (server module directly)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Optional providers
|
|
126
|
+
|
|
127
|
+
The default install supports the vector backend and HTTP-based embedding
|
|
128
|
+
providers (OpenAI / Ollama) once configured in the `yaml`. Heavier providers are
|
|
129
|
+
opt-in extras that mirror Engrava's own extras:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
uvx --from "engrava-mcp[local]" engrava-mcp # sentence-transformers (local model)
|
|
133
|
+
uvx --from "engrava-mcp[hf]" engrava-mcp # HuggingFace Inference API
|
|
134
|
+
uvx --from "engrava-mcp[openai]" engrava-mcp # OpenAI-compatible embeddings deps
|
|
135
|
+
uvx --from "engrava-mcp[ollama]" engrava-mcp # Ollama embeddings deps
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## The surface
|
|
139
|
+
|
|
140
|
+
- **Tools (11):** `get_thought`, `search_memory`, `search_keywords`,
|
|
141
|
+
`list_memory`, `query_memory`, `memory_stats` (read); `store_thought`,
|
|
142
|
+
`update_thought`, `link_thoughts`, `delete_thought`, `delete_edge` (write,
|
|
143
|
+
gated by `ENGRAVA_MCP_READ_ONLY`).
|
|
144
|
+
- **Resources (3):** `engrava://thought/{thought_id}`, `engrava://stats`,
|
|
145
|
+
`engrava://recent`.
|
|
146
|
+
- **Prompts (3):** `summarize_recent_memory`, `find_related`, `reflect_on_topic`.
|
|
147
|
+
|
|
148
|
+
`query_memory` accepts only MindQL `FIND` queries; raw SQL and every other
|
|
149
|
+
command are rejected.
|
|
150
|
+
|
|
151
|
+
## Development
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
pip install -e ".[dev]"
|
|
155
|
+
ruff check src/ tests/
|
|
156
|
+
ruff format --check src/ tests/
|
|
157
|
+
mypy --strict src/
|
|
158
|
+
pytest --cov --cov-fail-under=90
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## License
|
|
162
|
+
|
|
163
|
+
MIT
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "engrava-mcp"
|
|
7
|
+
version = "0.5.0"
|
|
8
|
+
description = "Model Context Protocol server for Engrava — expose an agent memory database to any MCP client over stdio."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "MIT"}
|
|
11
|
+
requires-python = ">=3.11"
|
|
12
|
+
authors = [{name = "Sovantica", email = "hello@sovantica.ai"}]
|
|
13
|
+
keywords = [
|
|
14
|
+
"mcp", "model-context-protocol", "ai-agents", "agent-memory",
|
|
15
|
+
"graph-memory", "engrava", "llm", "stdio-server",
|
|
16
|
+
]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Development Status :: 4 - Beta",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"License :: OSI Approved :: MIT License",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
26
|
+
"Typing :: Typed",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
dependencies = [
|
|
30
|
+
# Same-minor range on engrava's public API (engrava guarantees no breaking
|
|
31
|
+
# change within an X.Y.x patch range). 0.5.0 is the first release that ships
|
|
32
|
+
# engrava as a pure library with the public API this server consumes.
|
|
33
|
+
"engrava>=0.5,<0.6",
|
|
34
|
+
# Cap the MCP SDK major so a future incompatible release can't silently
|
|
35
|
+
# break `uvx engrava-mcp`; widen only after testing a new major. mcp>=1.27
|
|
36
|
+
# transitively constrains pydantic>=2.11,<3 (the floor engrava also targets).
|
|
37
|
+
"mcp>=1.27,<2",
|
|
38
|
+
# Default bundle for the bare `uvx engrava-mcp` (the registry one-click):
|
|
39
|
+
# the vector backend plus the http-based embedding providers' dependency,
|
|
40
|
+
# so semantic search and OpenAI/Ollama embeddings work out of the box once
|
|
41
|
+
# an embedding provider is declared in engrava.yaml. Heavy local providers
|
|
42
|
+
# (torch / sentence-transformers, HuggingFace Hub) stay opt-in extras.
|
|
43
|
+
"engrava[vec]>=0.5,<0.6",
|
|
44
|
+
"httpx>=0.27.0",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
[project.optional-dependencies]
|
|
48
|
+
# Mirror engrava's embedding-provider extras so users can install the matching
|
|
49
|
+
# provider dependencies alongside the server, e.g.
|
|
50
|
+
# `uvx --from "engrava-mcp[local]" engrava-mcp`.
|
|
51
|
+
openai = ["engrava[embeddings-openai]>=0.5,<0.6"]
|
|
52
|
+
ollama = ["engrava[embeddings-ollama]>=0.5,<0.6"]
|
|
53
|
+
hf = ["engrava[embeddings-hf]>=0.5,<0.6"]
|
|
54
|
+
local = ["engrava[embeddings-local]>=0.5,<0.6"]
|
|
55
|
+
dev = [
|
|
56
|
+
"pytest>=8.0.0",
|
|
57
|
+
"pytest-asyncio>=0.24.0",
|
|
58
|
+
"pytest-cov>=5.0.0",
|
|
59
|
+
"mypy>=1.11.0",
|
|
60
|
+
"ruff>=0.7.0",
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
[project.scripts]
|
|
64
|
+
engrava-mcp = "engrava_mcp.server:main"
|
|
65
|
+
|
|
66
|
+
[project.urls]
|
|
67
|
+
Homepage = "https://engrava.ai"
|
|
68
|
+
Documentation = "https://github.com/sovantica/engrava-mcp"
|
|
69
|
+
Repository = "https://github.com/sovantica/engrava-mcp"
|
|
70
|
+
Issues = "https://github.com/sovantica/engrava-mcp/issues"
|
|
71
|
+
|
|
72
|
+
[tool.setuptools.packages.find]
|
|
73
|
+
where = ["src"]
|
|
74
|
+
|
|
75
|
+
[tool.setuptools.package-data]
|
|
76
|
+
engrava_mcp = ["py.typed"]
|
|
77
|
+
|
|
78
|
+
[tool.ruff]
|
|
79
|
+
target-version = "py311"
|
|
80
|
+
line-length = 100
|
|
81
|
+
|
|
82
|
+
[tool.ruff.lint]
|
|
83
|
+
select = ["ALL"]
|
|
84
|
+
ignore = [
|
|
85
|
+
"D203",
|
|
86
|
+
"D213",
|
|
87
|
+
"COM812",
|
|
88
|
+
"ISC001",
|
|
89
|
+
"D107",
|
|
90
|
+
"TC001",
|
|
91
|
+
"PLR0913",
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
[tool.ruff.lint.per-file-ignores]
|
|
95
|
+
"tests/**" = ["S101", "PLR2004", "D", "SLF001", "PT011", "B017", "FBT", "PLC0415", "ARG001", "ARG002"]
|
|
96
|
+
|
|
97
|
+
[tool.mypy]
|
|
98
|
+
strict = true
|
|
99
|
+
warn_return_any = true
|
|
100
|
+
disallow_untyped_defs = true
|
|
101
|
+
no_implicit_reexport = true
|
|
102
|
+
# Type-check assuming the 3.11 floor (matches requires-python), so 3.12-only
|
|
103
|
+
# syntax can't leak into a package that claims to install on 3.11.
|
|
104
|
+
python_version = "3.11"
|
|
105
|
+
|
|
106
|
+
[[tool.mypy.overrides]]
|
|
107
|
+
module = "httpx.*"
|
|
108
|
+
ignore_missing_imports = true
|
|
109
|
+
|
|
110
|
+
[tool.pytest.ini_options]
|
|
111
|
+
asyncio_mode = "auto"
|
|
112
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Model Context Protocol server for engrava.
|
|
2
|
+
|
|
3
|
+
Exposes engrava's public read API as MCP tools over stdio, so MCP-aware
|
|
4
|
+
agents can fetch thoughts, search memory, and run structured queries.
|
|
5
|
+
The server is a standalone API consumer — it registers no engrava hooks,
|
|
6
|
+
manifests, or extensions.
|
|
7
|
+
|
|
8
|
+
Run it with ``python -m engrava_mcp`` or the ``engrava-mcp`` console
|
|
9
|
+
script.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from engrava_mcp._compat import incompatible_engrava_message
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
from engrava_mcp.server import build_server, main
|
|
18
|
+
except ImportError as exc: # pragma: no cover - raw trigger is un-triggerable in-process
|
|
19
|
+
# The server consumes engrava's PUBLIC API. A missing symbol here means an
|
|
20
|
+
# incompatible `engrava` is installed. We check API *presence*, not a
|
|
21
|
+
# version number, on purpose: engrava's source version lags its released
|
|
22
|
+
# version (a dev/editable install reports the pre-release number), so a
|
|
23
|
+
# numeric `>=0.5` assert would false-positive. Re-raise with an actionable
|
|
24
|
+
# message and preserve the original error. The message logic itself is
|
|
25
|
+
# covered by tests in ``tests/test_compat.py``.
|
|
26
|
+
raise ImportError(incompatible_engrava_message(exc)) from exc
|
|
27
|
+
|
|
28
|
+
__all__ = ["build_server", "main"]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""``python -m engrava_mcp`` entry point.
|
|
2
|
+
|
|
3
|
+
Delegates to :func:`engrava_mcp.server.main`, which builds the MCP server
|
|
4
|
+
and serves it over stdio.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from engrava_mcp.server import main
|
|
10
|
+
|
|
11
|
+
if __name__ == "__main__":
|
|
12
|
+
main()
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""Engrava compatibility helpers for the MCP server.
|
|
2
|
+
|
|
3
|
+
The server consumes engrava's PUBLIC API. Two compatibility concerns live
|
|
4
|
+
here so they are unit-testable in isolation from the import machinery and
|
|
5
|
+
the running server:
|
|
6
|
+
|
|
7
|
+
* :func:`incompatible_engrava_message` builds the actionable message the
|
|
8
|
+
package raises when the engrava public API cannot be imported at all (a
|
|
9
|
+
hard, fatal incompatibility caught in :mod:`engrava_mcp`'s ``__init__``).
|
|
10
|
+
* :func:`warn_if_engrava_out_of_range` emits a soft, non-fatal warning at
|
|
11
|
+
startup when the *installed* engrava version falls outside the range this
|
|
12
|
+
server is tested against. It never raises: a dev/editable engrava reports
|
|
13
|
+
a pre-release number whose public API is in practice compatible, and the
|
|
14
|
+
install-time dependency range is the real gate.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import importlib.metadata
|
|
20
|
+
import warnings
|
|
21
|
+
|
|
22
|
+
#: Lowest engrava version (inclusive) this server is tested against.
|
|
23
|
+
#: Keep in sync with the ``engrava>=0.5,<0.6`` requirement in ``pyproject.toml``.
|
|
24
|
+
ENGRAVA_MIN_VERSION = (0, 5)
|
|
25
|
+
|
|
26
|
+
#: Lowest engrava version (exclusive) that is out of range — the next minor.
|
|
27
|
+
#: Keep in sync with the ``engrava>=0.5,<0.6`` requirement in ``pyproject.toml``.
|
|
28
|
+
ENGRAVA_MAX_VERSION_EXCLUSIVE = (0, 6)
|
|
29
|
+
|
|
30
|
+
#: Human-readable supported range, used in user-facing messages. Keep in sync
|
|
31
|
+
#: with the ``engrava>=0.5,<0.6`` requirement in ``pyproject.toml``.
|
|
32
|
+
ENGRAVA_SUPPORTED_RANGE = ">=0.5,<0.6"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class EngravaVersionWarning(UserWarning):
|
|
36
|
+
"""Warn that the installed engrava version is outside the tested range.
|
|
37
|
+
|
|
38
|
+
Emitted by :func:`warn_if_engrava_out_of_range` at server startup. It is
|
|
39
|
+
a warning, never an error: behaviour with an untested engrava version is
|
|
40
|
+
unverified rather than known-broken.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def incompatible_engrava_message(exc: BaseException) -> str:
|
|
45
|
+
"""Build the actionable message for an unimportable engrava public API.
|
|
46
|
+
|
|
47
|
+
The server consumes engrava's PUBLIC API. A missing symbol when importing
|
|
48
|
+
it means an incompatible ``engrava`` is installed. This builds a message
|
|
49
|
+
that names the dependency, states the supported range, tells the operator
|
|
50
|
+
how to recover, and preserves the original error for diagnosis.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
exc: The original import-time exception to quote in the message.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
An actionable, single-string error message.
|
|
57
|
+
|
|
58
|
+
"""
|
|
59
|
+
return (
|
|
60
|
+
"engrava-mcp could not load the engrava public API it depends on. "
|
|
61
|
+
"This usually means an incompatible 'engrava' is installed — engrava-mcp "
|
|
62
|
+
f"requires engrava {ENGRAVA_SUPPORTED_RANGE}. Reinstall engrava-mcp "
|
|
63
|
+
"(which pulls a compatible engrava) or align your engrava version. "
|
|
64
|
+
f"Original import error: {exc}"
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _parse_major_minor(version: str) -> tuple[int, int] | None:
|
|
69
|
+
"""Parse the leading ``major.minor`` pair from a version string.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
version: A version string, e.g. ``"0.5.2"`` or ``"0.6.0rc1"``.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
The ``(major, minor)`` tuple, or ``None`` if it cannot be parsed.
|
|
76
|
+
|
|
77
|
+
"""
|
|
78
|
+
parts = version.split(".")
|
|
79
|
+
if len(parts) < 2: # noqa: PLR2004 - a version needs at least major.minor
|
|
80
|
+
return None
|
|
81
|
+
try:
|
|
82
|
+
major = int(parts[0])
|
|
83
|
+
except ValueError:
|
|
84
|
+
return None
|
|
85
|
+
# Strip any pre-release/build suffix on the minor segment (e.g. "6rc1").
|
|
86
|
+
minor_digits = ""
|
|
87
|
+
for char in parts[1]:
|
|
88
|
+
if not char.isdigit():
|
|
89
|
+
break
|
|
90
|
+
minor_digits += char
|
|
91
|
+
if not minor_digits:
|
|
92
|
+
return None
|
|
93
|
+
return (major, int(minor_digits))
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def warn_if_engrava_out_of_range() -> None:
|
|
97
|
+
"""Warn if the installed engrava version is outside the tested range.
|
|
98
|
+
|
|
99
|
+
Reads the installed engrava version via :func:`importlib.metadata.version`
|
|
100
|
+
and, if its ``major.minor`` is outside the supported range
|
|
101
|
+
(:data:`ENGRAVA_SUPPORTED_RANGE`), emits an :class:`EngravaVersionWarning`.
|
|
102
|
+
It never raises: an unparseable or missing version, or an out-of-range one,
|
|
103
|
+
only warns. Call this once at startup, not at module import, to avoid noise
|
|
104
|
+
on every ``import engrava_mcp``.
|
|
105
|
+
"""
|
|
106
|
+
try:
|
|
107
|
+
installed = importlib.metadata.version("engrava")
|
|
108
|
+
except importlib.metadata.PackageNotFoundError:
|
|
109
|
+
# No distribution metadata (unusual in practice). The install-time
|
|
110
|
+
# dependency range is the real gate; stay silent rather than warn on a
|
|
111
|
+
# missing-metadata edge that does not imply incompatibility.
|
|
112
|
+
return
|
|
113
|
+
|
|
114
|
+
parsed = _parse_major_minor(installed)
|
|
115
|
+
if parsed is None:
|
|
116
|
+
return
|
|
117
|
+
|
|
118
|
+
if not (ENGRAVA_MIN_VERSION <= parsed < ENGRAVA_MAX_VERSION_EXCLUSIVE):
|
|
119
|
+
warnings.warn(
|
|
120
|
+
f"Installed engrava version {installed} is outside engrava-mcp's "
|
|
121
|
+
f"tested range ({ENGRAVA_SUPPORTED_RANGE}); behaviour is unverified. "
|
|
122
|
+
"Align your engrava version if you hit unexpected results.",
|
|
123
|
+
EngravaVersionWarning,
|
|
124
|
+
stacklevel=2,
|
|
125
|
+
)
|