memoryintelligence-mcp 0.1.6__tar.gz → 0.1.8__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.
- {memoryintelligence_mcp-0.1.6 → memoryintelligence_mcp-0.1.8}/CHANGELOG.md +41 -5
- {memoryintelligence_mcp-0.1.6 → memoryintelligence_mcp-0.1.8}/PKG-INFO +51 -20
- {memoryintelligence_mcp-0.1.6 → memoryintelligence_mcp-0.1.8}/README.md +50 -19
- {memoryintelligence_mcp-0.1.6 → memoryintelligence_mcp-0.1.8}/pyproject.toml +1 -1
- {memoryintelligence_mcp-0.1.6 → memoryintelligence_mcp-0.1.8}/src/mi_mcp/__init__.py +1 -1
- {memoryintelligence_mcp-0.1.6 → memoryintelligence_mcp-0.1.8}/src/mi_mcp/cli.py +65 -36
- memoryintelligence_mcp-0.1.8/src/mi_mcp/paths.py +129 -0
- {memoryintelligence_mcp-0.1.6 → memoryintelligence_mcp-0.1.8}/src/mi_mcp/server.py +3 -3
- memoryintelligence_mcp-0.1.6/src/mi_mcp/paths.py +0 -86
- {memoryintelligence_mcp-0.1.6 → memoryintelligence_mcp-0.1.8}/.gitignore +0 -0
- {memoryintelligence_mcp-0.1.6 → memoryintelligence_mcp-0.1.8}/CONTRIBUTING.md +0 -0
- {memoryintelligence_mcp-0.1.6 → memoryintelligence_mcp-0.1.8}/LICENSE +0 -0
- {memoryintelligence_mcp-0.1.6 → memoryintelligence_mcp-0.1.8}/src/mi_mcp/__main__.py +0 -0
- {memoryintelligence_mcp-0.1.6 → memoryintelligence_mcp-0.1.8}/src/mi_mcp/client.py +0 -0
- {memoryintelligence_mcp-0.1.6 → memoryintelligence_mcp-0.1.8}/src/mi_mcp/config.py +0 -0
- {memoryintelligence_mcp-0.1.6 → memoryintelligence_mcp-0.1.8}/src/mi_mcp/keys.py +0 -0
- {memoryintelligence_mcp-0.1.6 → memoryintelligence_mcp-0.1.8}/src/mi_mcp/umo_format.py +0 -0
- {memoryintelligence_mcp-0.1.6 → memoryintelligence_mcp-0.1.8}/src/mi_mcp/vault.py +0 -0
|
@@ -3,6 +3,41 @@
|
|
|
3
3
|
All notable changes to `memoryintelligence-mcp` are documented here.
|
|
4
4
|
The format follows [Keep a Changelog](https://keepachangelog.com/); this project uses [Semantic Versioning](https://semver.org/).
|
|
5
5
|
|
|
6
|
+
## [0.1.8] — 2026-06-10
|
|
7
|
+
|
|
8
|
+
### Changed
|
|
9
|
+
- **The MCP server id is now `memoryintelligence`** (was `memory-intelligence`).
|
|
10
|
+
This is the id under `mcpServers` in your config and the name in
|
|
11
|
+
`claude mcp add …`. It now matches the brand/package token everywhere else
|
|
12
|
+
(`MemoryIntelligence`, `memoryintelligence-mcp`) instead of splitting the word
|
|
13
|
+
with a dash.
|
|
14
|
+
- **Auto-migration:** `mi-mcp wire`/`setup` removes the old `memory-intelligence`
|
|
15
|
+
entry from every surface (file configs **and** `claude mcp remove`) before
|
|
16
|
+
adding the new id, so an upgrade leaves no duplicate/orphan. `mi-mcp doctor`
|
|
17
|
+
flags a leftover legacy entry and tells you to re-wire. **Action on upgrade:
|
|
18
|
+
run `mi-mcp wire` once.** (Hand-edited configs: rename the key yourself.)
|
|
19
|
+
|
|
20
|
+
## [0.1.7] — 2026-06-09
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
- **Hidden files now live under `~/.memoryintelligence/`** (was `~/.mi/`), matching
|
|
24
|
+
the visible `~/MemoryIntelligence/` vault — one on-brand namespace:
|
|
25
|
+
- launcher → `~/.memoryintelligence/mcp/run-mi-mcp.sh`
|
|
26
|
+
- capture opt-in allowlist → `~/.memoryintelligence/mcp/opt-in-paths`
|
|
27
|
+
- keyfile (the Keychain fallback) → `~/.memoryintelligence/.env`
|
|
28
|
+
|
|
29
|
+
`mi-mcp setup`/`wire` write the new layout and migrate an existing
|
|
30
|
+
`~/.mi/opt-in-paths` forward non-destructively. The legacy `~/.mi/` launcher
|
|
31
|
+
and `~/.mi-env` keyfile are still **read** for one release, so existing
|
|
32
|
+
installs keep working until you re-run `wire`. `paths.py` is now the single
|
|
33
|
+
source of truth for the layout (it previously declared the new layout while the
|
|
34
|
+
CLI still wrote `~/.mi/` — that split is fixed here).
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
- **README "Names & locations" map** — explains the package / command / server-id
|
|
38
|
+
names (`memoryintelligence-mcp` / `mi-mcp` / `memory-intelligence`) and where
|
|
39
|
+
every file lives, so the naming no longer looks arbitrary.
|
|
40
|
+
|
|
6
41
|
## [0.1.6] — 2026-06-09
|
|
7
42
|
|
|
8
43
|
### Security
|
|
@@ -12,11 +47,12 @@ The format follows [Keep a Changelog](https://keepachangelog.com/); this project
|
|
|
12
47
|
the key resolved from (Keychain / keyfile / env), never the key itself.
|
|
13
48
|
|
|
14
49
|
### Added
|
|
15
|
-
- **MCP ↔ API contract tests** (`tests/test_api_contract.py
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
bool→enum bug
|
|
50
|
+
- **MCP ↔ API contract tests** (`tests/test_api_contract.py`, run in CI — not
|
|
51
|
+
shipped in the published package) — pin that every client method sends only
|
|
52
|
+
parameter values the API accepts (`explain`, `pii_handling`,
|
|
53
|
+
`retention_policy`, `scope`), so an API enum/type change can never silently
|
|
54
|
+
422 a real call. This is the general form of the `explain` bool→enum bug
|
|
55
|
+
fixed in 0.1.3.
|
|
20
56
|
|
|
21
57
|
### Changed
|
|
22
58
|
- Internal code-quality cleanups (narrowed a few broad `except` clauses, removed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: memoryintelligence-mcp
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.8
|
|
4
4
|
Summary: MCP server for MemoryIntelligence — give Claude a memory you own, set up in one command
|
|
5
5
|
Project-URL: Homepage, https://memoryintelligence.io
|
|
6
6
|
Project-URL: Repository, https://github.com/somewhere11/memoryintelligence-mcp
|
|
@@ -72,7 +72,7 @@ API key is ever written into a config file.**
|
|
|
72
72
|
|
|
73
73
|
---
|
|
74
74
|
|
|
75
|
-
## ✅ What works today (0.1.
|
|
75
|
+
## ✅ What works today (0.1.7)
|
|
76
76
|
|
|
77
77
|
Honest status — this is beta, so here's exactly what's live:
|
|
78
78
|
|
|
@@ -177,7 +177,7 @@ provenance — runs in the service. Your API key authenticates to *your* account
|
|
|
177
177
|
|
|
178
178
|
1. **prompts for your API key** (hidden input);
|
|
179
179
|
2. **stores it securely, outside every config** — macOS **Keychain**, or a
|
|
180
|
-
`chmod 600 ~/.
|
|
180
|
+
`chmod 600 ~/.memoryintelligence/.env` keyfile on Linux/Windows (or with `--store file`);
|
|
181
181
|
3. **wires** the server into Claude Desktop + Claude Code (`--surfaces` to choose
|
|
182
182
|
`desktop,code,cursor`), writing `env: {}` — **no key in any config file**;
|
|
183
183
|
4. **opts in** the current directory so captures are allowed there (reads work
|
|
@@ -187,7 +187,7 @@ provenance — runs in the service. Your API key authenticates to *your* account
|
|
|
187
187
|
```bash
|
|
188
188
|
mi-mcp setup # the happy path (interactive)
|
|
189
189
|
mi-mcp setup --surfaces desktop,code,cursor
|
|
190
|
-
mi-mcp setup --store file # force the ~/.
|
|
190
|
+
mi-mcp setup --store file # force the ~/.memoryintelligence/.env keyfile (e.g. on Linux)
|
|
191
191
|
mi-mcp setup --no-opt-in # wire only; opt a folder in later
|
|
192
192
|
```
|
|
193
193
|
|
|
@@ -202,12 +202,12 @@ mi-mcp wire --dry-run # preview wiring changes without writing
|
|
|
202
202
|
|
|
203
203
|
### How the key stays out of your configs
|
|
204
204
|
|
|
205
|
-
`wire` points each host at a small launcher (`~/.
|
|
206
|
-
`MI_API_KEY` **at launch**, in order:
|
|
205
|
+
`wire` points each host at a small launcher (`~/.memoryintelligence/mcp/run-mi-mcp.sh`)
|
|
206
|
+
that resolves `MI_API_KEY` **at launch**, in order:
|
|
207
207
|
|
|
208
208
|
1. the process environment, then
|
|
209
209
|
2. the macOS **Keychain** (`security find-generic-password -s MI_API_KEY`), then
|
|
210
|
-
3. a
|
|
210
|
+
3. a `chmod 600` keyfile (`~/.memoryintelligence/.env`, or the legacy `~/.mi-env`), else it fails.
|
|
211
211
|
|
|
212
212
|
So a leaked or committed config file exposes **nothing**.
|
|
213
213
|
|
|
@@ -220,12 +220,12 @@ So a leaked or committed config file exposes **nothing**.
|
|
|
220
220
|
## Security
|
|
221
221
|
|
|
222
222
|
- **No key in configs.** `setup`/`wire` write `env: {}`; the launcher resolves the
|
|
223
|
-
key from the Keychain (or `~/.
|
|
224
|
-
config file.
|
|
223
|
+
key from the Keychain (or the `~/.memoryintelligence/.env` keyfile) at runtime.
|
|
224
|
+
Nothing sensitive lands in a config file.
|
|
225
225
|
- **Capture is opt-in per directory.** Write tools (`mi_capture`/`mi_batch`/
|
|
226
226
|
`mi_upload`) only run when the server's working directory is on the
|
|
227
|
-
`~/.
|
|
228
|
-
captures are skipped.
|
|
227
|
+
`~/.memoryintelligence/mcp/opt-in-paths` allowlist. Reads are never gated. Absent
|
|
228
|
+
allowlist → all captures are skipped.
|
|
229
229
|
- **Destructive ops require confirmation.** `mi_forget` (irreversible delete)
|
|
230
230
|
requires an explicit `confirm=true` argument — a human-in-the-loop guard against
|
|
231
231
|
injected or accidental deletes.
|
|
@@ -248,8 +248,8 @@ So a leaked or committed config file exposes **nothing**.
|
|
|
248
248
|
- **Privacy.** Content you capture is sent to your MemoryIntelligence account over
|
|
249
249
|
HTTPS; nothing else is transmitted, and the server does not log conversation
|
|
250
250
|
content or your API key. See [memoryintelligence.io/privacy](https://memoryintelligence.io/privacy).
|
|
251
|
-
- **Off switch.** Clear `~/.
|
|
252
|
-
`
|
|
251
|
+
- **Off switch.** Clear `~/.memoryintelligence/mcp/opt-in-paths` (captures skip) or
|
|
252
|
+
remove the `memoryintelligence` entry from your Claude config to fully unwire.
|
|
253
253
|
|
|
254
254
|
Found a vulnerability? See [SECURITY.md](SECURITY.md) — report privately to
|
|
255
255
|
connect@somewheremedia.com.
|
|
@@ -258,7 +258,7 @@ connect@somewheremedia.com.
|
|
|
258
258
|
|
|
259
259
|
| Variable | Required | Default | Description |
|
|
260
260
|
|----------|----------|---------|-------------|
|
|
261
|
-
| `MI_API_KEY` | Yes | — | Your MI API key (resolved by the launcher from the Keychain / `~/.
|
|
261
|
+
| `MI_API_KEY` | Yes | — | Your MI API key (resolved by the launcher from the Keychain / `~/.memoryintelligence/.env` — don't set inline in configs) |
|
|
262
262
|
| `MI_BASE_URL` | No | `https://api.memoryintelligence.io` | API base URL |
|
|
263
263
|
| `MI_MCP_FULL` | No | _(off)_ | `1` exposes all 10 tools; otherwise only the 3 core |
|
|
264
264
|
| `MI_TRANSPORT` | No | `stdio` | `stdio` only in this version (networked transports disabled) |
|
|
@@ -268,6 +268,35 @@ connect@somewheremedia.com.
|
|
|
268
268
|
| `MI_DEFAULT_RETENTION` | No | `meaning_only` | Default retention policy |
|
|
269
269
|
| `MI_DEFAULT_PII_HANDLING` | No | `extract_and_redact` | Default PII handling |
|
|
270
270
|
|
|
271
|
+
## Names & locations
|
|
272
|
+
|
|
273
|
+
You'll see a few related names. They differ because each ecosystem has its own
|
|
274
|
+
rules (PyPI lowercases, Python imports can't contain hyphens, MCP server ids are
|
|
275
|
+
lowercase-hyphen) — but they collapse to one **long form** and one **short form**:
|
|
276
|
+
|
|
277
|
+
| You see | What it is | Why this form |
|
|
278
|
+
|---|---|---|
|
|
279
|
+
| `MemoryIntelligence` | the brand | display name |
|
|
280
|
+
| `memoryintelligence-mcp` | the **PyPI package** (`pip install` / `uvx`) | PyPI normalizes to lowercase + hyphens |
|
|
281
|
+
| `mi-mcp` | the **command** you run (`mi-mcp setup`) | short for daily use (`memoryintelligence-mcp` is an alias) |
|
|
282
|
+
| `mi_mcp` | the Python import package | must be a valid identifier — no hyphens |
|
|
283
|
+
| `memoryintelligence` | the **server id** in your MCP config | matches the brand token (renamed from `memory-intelligence` in 0.1.8) |
|
|
284
|
+
| `MI_*` (e.g. `MI_API_KEY`) | environment variables / Keychain service | short prefix |
|
|
285
|
+
|
|
286
|
+
And everything written to disk lives under one on-brand namespace:
|
|
287
|
+
|
|
288
|
+
| Path | What |
|
|
289
|
+
|---|---|
|
|
290
|
+
| `~/MemoryIntelligence/` | visible vault — your `.umo` files (override with `MI_VAULT`) |
|
|
291
|
+
| `~/.memoryintelligence/mcp/run-mi-mcp.sh` | the launcher each MCP host spawns |
|
|
292
|
+
| `~/.memoryintelligence/mcp/opt-in-paths` | per-directory capture allowlist |
|
|
293
|
+
| `~/.memoryintelligence/.env` | `chmod 600` keyfile (the Keychain fallback) |
|
|
294
|
+
| macOS Keychain (`MI_API_KEY`) | preferred key storage — never on disk |
|
|
295
|
+
|
|
296
|
+
> Upgrading from ≤ 0.1.6? The old `~/.mi/` launcher and `~/.mi-env` keyfile still
|
|
297
|
+
> work (they're read as a fallback). Re-run `mi-mcp wire` to move to the new paths;
|
|
298
|
+
> your opt-in list is migrated forward automatically.
|
|
299
|
+
|
|
271
300
|
## Manual setup (cross-platform / advanced)
|
|
272
301
|
|
|
273
302
|
`mi-mcp setup` is the recommended path on every OS. If you'd rather do it by hand
|
|
@@ -279,15 +308,16 @@ then run `mi-mcp wire`:
|
|
|
279
308
|
```bash
|
|
280
309
|
read -s K; security add-generic-password -a "$USER" -s "MI_API_KEY" -w "$K" -U; unset K
|
|
281
310
|
mi-mcp wire
|
|
282
|
-
echo "$(pwd)" >> ~/.
|
|
311
|
+
echo "$(pwd)" >> ~/.memoryintelligence/mcp/opt-in-paths
|
|
283
312
|
```
|
|
284
313
|
|
|
285
|
-
**Linux / Windows (WSL or Git Bash) — `~/.
|
|
314
|
+
**Linux / Windows (WSL or Git Bash) — `~/.memoryintelligence/.env` keyfile:**
|
|
286
315
|
|
|
287
316
|
```bash
|
|
288
|
-
|
|
317
|
+
mkdir -p ~/.memoryintelligence
|
|
318
|
+
umask 077 && printf 'MI_API_KEY="%s"\n' "$YOUR_KEY" > ~/.memoryintelligence/.env # chmod 600
|
|
289
319
|
mi-mcp wire
|
|
290
|
-
echo "$(pwd)" >> ~/.
|
|
320
|
+
echo "$(pwd)" >> ~/.memoryintelligence/mcp/opt-in-paths
|
|
291
321
|
```
|
|
292
322
|
|
|
293
323
|
**Or just an environment variable** (any OS — exported in your shell profile):
|
|
@@ -297,8 +327,9 @@ export MI_API_KEY="mi_sk_..." # the launcher reads the inherited env first
|
|
|
297
327
|
```
|
|
298
328
|
|
|
299
329
|
The launcher resolves the key in order: **inherited env → macOS Keychain →
|
|
300
|
-
`~/.
|
|
301
|
-
|
|
330
|
+
`~/.memoryintelligence/.env`** (the legacy `~/.mi-env` is still read).
|
|
331
|
+
`security add-generic-password` is macOS-only, so on Linux/Windows use the keyfile
|
|
332
|
+
or env var — never paste the key into an MCP client config.
|
|
302
333
|
|
|
303
334
|
## Development
|
|
304
335
|
|
|
@@ -37,7 +37,7 @@ API key is ever written into a config file.**
|
|
|
37
37
|
|
|
38
38
|
---
|
|
39
39
|
|
|
40
|
-
## ✅ What works today (0.1.
|
|
40
|
+
## ✅ What works today (0.1.7)
|
|
41
41
|
|
|
42
42
|
Honest status — this is beta, so here's exactly what's live:
|
|
43
43
|
|
|
@@ -142,7 +142,7 @@ provenance — runs in the service. Your API key authenticates to *your* account
|
|
|
142
142
|
|
|
143
143
|
1. **prompts for your API key** (hidden input);
|
|
144
144
|
2. **stores it securely, outside every config** — macOS **Keychain**, or a
|
|
145
|
-
`chmod 600 ~/.
|
|
145
|
+
`chmod 600 ~/.memoryintelligence/.env` keyfile on Linux/Windows (or with `--store file`);
|
|
146
146
|
3. **wires** the server into Claude Desktop + Claude Code (`--surfaces` to choose
|
|
147
147
|
`desktop,code,cursor`), writing `env: {}` — **no key in any config file**;
|
|
148
148
|
4. **opts in** the current directory so captures are allowed there (reads work
|
|
@@ -152,7 +152,7 @@ provenance — runs in the service. Your API key authenticates to *your* account
|
|
|
152
152
|
```bash
|
|
153
153
|
mi-mcp setup # the happy path (interactive)
|
|
154
154
|
mi-mcp setup --surfaces desktop,code,cursor
|
|
155
|
-
mi-mcp setup --store file # force the ~/.
|
|
155
|
+
mi-mcp setup --store file # force the ~/.memoryintelligence/.env keyfile (e.g. on Linux)
|
|
156
156
|
mi-mcp setup --no-opt-in # wire only; opt a folder in later
|
|
157
157
|
```
|
|
158
158
|
|
|
@@ -167,12 +167,12 @@ mi-mcp wire --dry-run # preview wiring changes without writing
|
|
|
167
167
|
|
|
168
168
|
### How the key stays out of your configs
|
|
169
169
|
|
|
170
|
-
`wire` points each host at a small launcher (`~/.
|
|
171
|
-
`MI_API_KEY` **at launch**, in order:
|
|
170
|
+
`wire` points each host at a small launcher (`~/.memoryintelligence/mcp/run-mi-mcp.sh`)
|
|
171
|
+
that resolves `MI_API_KEY` **at launch**, in order:
|
|
172
172
|
|
|
173
173
|
1. the process environment, then
|
|
174
174
|
2. the macOS **Keychain** (`security find-generic-password -s MI_API_KEY`), then
|
|
175
|
-
3. a
|
|
175
|
+
3. a `chmod 600` keyfile (`~/.memoryintelligence/.env`, or the legacy `~/.mi-env`), else it fails.
|
|
176
176
|
|
|
177
177
|
So a leaked or committed config file exposes **nothing**.
|
|
178
178
|
|
|
@@ -185,12 +185,12 @@ So a leaked or committed config file exposes **nothing**.
|
|
|
185
185
|
## Security
|
|
186
186
|
|
|
187
187
|
- **No key in configs.** `setup`/`wire` write `env: {}`; the launcher resolves the
|
|
188
|
-
key from the Keychain (or `~/.
|
|
189
|
-
config file.
|
|
188
|
+
key from the Keychain (or the `~/.memoryintelligence/.env` keyfile) at runtime.
|
|
189
|
+
Nothing sensitive lands in a config file.
|
|
190
190
|
- **Capture is opt-in per directory.** Write tools (`mi_capture`/`mi_batch`/
|
|
191
191
|
`mi_upload`) only run when the server's working directory is on the
|
|
192
|
-
`~/.
|
|
193
|
-
captures are skipped.
|
|
192
|
+
`~/.memoryintelligence/mcp/opt-in-paths` allowlist. Reads are never gated. Absent
|
|
193
|
+
allowlist → all captures are skipped.
|
|
194
194
|
- **Destructive ops require confirmation.** `mi_forget` (irreversible delete)
|
|
195
195
|
requires an explicit `confirm=true` argument — a human-in-the-loop guard against
|
|
196
196
|
injected or accidental deletes.
|
|
@@ -213,8 +213,8 @@ So a leaked or committed config file exposes **nothing**.
|
|
|
213
213
|
- **Privacy.** Content you capture is sent to your MemoryIntelligence account over
|
|
214
214
|
HTTPS; nothing else is transmitted, and the server does not log conversation
|
|
215
215
|
content or your API key. See [memoryintelligence.io/privacy](https://memoryintelligence.io/privacy).
|
|
216
|
-
- **Off switch.** Clear `~/.
|
|
217
|
-
`
|
|
216
|
+
- **Off switch.** Clear `~/.memoryintelligence/mcp/opt-in-paths` (captures skip) or
|
|
217
|
+
remove the `memoryintelligence` entry from your Claude config to fully unwire.
|
|
218
218
|
|
|
219
219
|
Found a vulnerability? See [SECURITY.md](SECURITY.md) — report privately to
|
|
220
220
|
connect@somewheremedia.com.
|
|
@@ -223,7 +223,7 @@ connect@somewheremedia.com.
|
|
|
223
223
|
|
|
224
224
|
| Variable | Required | Default | Description |
|
|
225
225
|
|----------|----------|---------|-------------|
|
|
226
|
-
| `MI_API_KEY` | Yes | — | Your MI API key (resolved by the launcher from the Keychain / `~/.
|
|
226
|
+
| `MI_API_KEY` | Yes | — | Your MI API key (resolved by the launcher from the Keychain / `~/.memoryintelligence/.env` — don't set inline in configs) |
|
|
227
227
|
| `MI_BASE_URL` | No | `https://api.memoryintelligence.io` | API base URL |
|
|
228
228
|
| `MI_MCP_FULL` | No | _(off)_ | `1` exposes all 10 tools; otherwise only the 3 core |
|
|
229
229
|
| `MI_TRANSPORT` | No | `stdio` | `stdio` only in this version (networked transports disabled) |
|
|
@@ -233,6 +233,35 @@ connect@somewheremedia.com.
|
|
|
233
233
|
| `MI_DEFAULT_RETENTION` | No | `meaning_only` | Default retention policy |
|
|
234
234
|
| `MI_DEFAULT_PII_HANDLING` | No | `extract_and_redact` | Default PII handling |
|
|
235
235
|
|
|
236
|
+
## Names & locations
|
|
237
|
+
|
|
238
|
+
You'll see a few related names. They differ because each ecosystem has its own
|
|
239
|
+
rules (PyPI lowercases, Python imports can't contain hyphens, MCP server ids are
|
|
240
|
+
lowercase-hyphen) — but they collapse to one **long form** and one **short form**:
|
|
241
|
+
|
|
242
|
+
| You see | What it is | Why this form |
|
|
243
|
+
|---|---|---|
|
|
244
|
+
| `MemoryIntelligence` | the brand | display name |
|
|
245
|
+
| `memoryintelligence-mcp` | the **PyPI package** (`pip install` / `uvx`) | PyPI normalizes to lowercase + hyphens |
|
|
246
|
+
| `mi-mcp` | the **command** you run (`mi-mcp setup`) | short for daily use (`memoryintelligence-mcp` is an alias) |
|
|
247
|
+
| `mi_mcp` | the Python import package | must be a valid identifier — no hyphens |
|
|
248
|
+
| `memoryintelligence` | the **server id** in your MCP config | matches the brand token (renamed from `memory-intelligence` in 0.1.8) |
|
|
249
|
+
| `MI_*` (e.g. `MI_API_KEY`) | environment variables / Keychain service | short prefix |
|
|
250
|
+
|
|
251
|
+
And everything written to disk lives under one on-brand namespace:
|
|
252
|
+
|
|
253
|
+
| Path | What |
|
|
254
|
+
|---|---|
|
|
255
|
+
| `~/MemoryIntelligence/` | visible vault — your `.umo` files (override with `MI_VAULT`) |
|
|
256
|
+
| `~/.memoryintelligence/mcp/run-mi-mcp.sh` | the launcher each MCP host spawns |
|
|
257
|
+
| `~/.memoryintelligence/mcp/opt-in-paths` | per-directory capture allowlist |
|
|
258
|
+
| `~/.memoryintelligence/.env` | `chmod 600` keyfile (the Keychain fallback) |
|
|
259
|
+
| macOS Keychain (`MI_API_KEY`) | preferred key storage — never on disk |
|
|
260
|
+
|
|
261
|
+
> Upgrading from ≤ 0.1.6? The old `~/.mi/` launcher and `~/.mi-env` keyfile still
|
|
262
|
+
> work (they're read as a fallback). Re-run `mi-mcp wire` to move to the new paths;
|
|
263
|
+
> your opt-in list is migrated forward automatically.
|
|
264
|
+
|
|
236
265
|
## Manual setup (cross-platform / advanced)
|
|
237
266
|
|
|
238
267
|
`mi-mcp setup` is the recommended path on every OS. If you'd rather do it by hand
|
|
@@ -244,15 +273,16 @@ then run `mi-mcp wire`:
|
|
|
244
273
|
```bash
|
|
245
274
|
read -s K; security add-generic-password -a "$USER" -s "MI_API_KEY" -w "$K" -U; unset K
|
|
246
275
|
mi-mcp wire
|
|
247
|
-
echo "$(pwd)" >> ~/.
|
|
276
|
+
echo "$(pwd)" >> ~/.memoryintelligence/mcp/opt-in-paths
|
|
248
277
|
```
|
|
249
278
|
|
|
250
|
-
**Linux / Windows (WSL or Git Bash) — `~/.
|
|
279
|
+
**Linux / Windows (WSL or Git Bash) — `~/.memoryintelligence/.env` keyfile:**
|
|
251
280
|
|
|
252
281
|
```bash
|
|
253
|
-
|
|
282
|
+
mkdir -p ~/.memoryintelligence
|
|
283
|
+
umask 077 && printf 'MI_API_KEY="%s"\n' "$YOUR_KEY" > ~/.memoryintelligence/.env # chmod 600
|
|
254
284
|
mi-mcp wire
|
|
255
|
-
echo "$(pwd)" >> ~/.
|
|
285
|
+
echo "$(pwd)" >> ~/.memoryintelligence/mcp/opt-in-paths
|
|
256
286
|
```
|
|
257
287
|
|
|
258
288
|
**Or just an environment variable** (any OS — exported in your shell profile):
|
|
@@ -262,8 +292,9 @@ export MI_API_KEY="mi_sk_..." # the launcher reads the inherited env first
|
|
|
262
292
|
```
|
|
263
293
|
|
|
264
294
|
The launcher resolves the key in order: **inherited env → macOS Keychain →
|
|
265
|
-
`~/.
|
|
266
|
-
|
|
295
|
+
`~/.memoryintelligence/.env`** (the legacy `~/.mi-env` is still read).
|
|
296
|
+
`security add-generic-password` is macOS-only, so on Linux/Windows use the keyfile
|
|
297
|
+
or env var — never paste the key into an MCP client config.
|
|
267
298
|
|
|
268
299
|
## Development
|
|
269
300
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "memoryintelligence-mcp"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.8"
|
|
8
8
|
description = "MCP server for MemoryIntelligence — give Claude a memory you own, set up in one command"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Security model (build-spec §13.3): the API key NEVER goes into any MCP client
|
|
4
4
|
config. ``wire`` writes ``{command: <wrapper>, args: [], env: {}}`` and renders
|
|
5
|
-
a wrapper (``~/.
|
|
6
|
-
the macOS Keychain, then
|
|
7
|
-
exposes nothing.
|
|
5
|
+
a wrapper (``~/.memoryintelligence/mcp/run-mi-mcp.sh``) that resolves
|
|
6
|
+
``MI_API_KEY`` at launch from the macOS Keychain, then the on-disk keyfile,
|
|
7
|
+
else fails. So a leaked config file exposes nothing.
|
|
8
8
|
|
|
9
9
|
setup — one command: store the key, wire hosts, opt in this dir, verify.
|
|
10
10
|
(alias: ``init``). The frictionless front door for new users.
|
|
@@ -13,8 +13,12 @@ exposes nothing.
|
|
|
13
13
|
status — show which surfaces are wired + the capture opt-in allowlist.
|
|
14
14
|
|
|
15
15
|
The key is stored OUTSIDE every config — in the macOS Keychain (``security``)
|
|
16
|
-
or, on Linux/Windows (or by choice), a ``chmod 600 ~/.
|
|
16
|
+
or, on Linux/Windows (or by choice), a ``chmod 600 ~/.memoryintelligence/.env``
|
|
17
|
+
keyfile (the legacy ``~/.mi-env`` is still read for back-compat). Both are
|
|
17
18
|
resolved by the launch wrapper at runtime, so no config file ever holds a secret.
|
|
19
|
+
|
|
20
|
+
The on-disk layout (launcher, opt-in allowlist, keyfile) lives under
|
|
21
|
+
``~/.memoryintelligence/`` — see ``paths.py``, the single source of truth.
|
|
18
22
|
"""
|
|
19
23
|
|
|
20
24
|
from __future__ import annotations
|
|
@@ -29,9 +33,14 @@ import sys
|
|
|
29
33
|
import tempfile
|
|
30
34
|
from pathlib import Path
|
|
31
35
|
|
|
36
|
+
from . import paths
|
|
32
37
|
from .config import load_opt_in_paths
|
|
33
38
|
|
|
34
|
-
SERVER_KEY = "
|
|
39
|
+
SERVER_KEY = "memoryintelligence"
|
|
40
|
+
# Pre-0.1.8 server ids we replace on wire so an upgrade leaves no orphaned entry.
|
|
41
|
+
# (0.1.7 and earlier registered the server as "memory-intelligence" — the one
|
|
42
|
+
# dash-seam that didn't match the brand/package "memoryintelligence".)
|
|
43
|
+
LEGACY_SERVER_KEYS = ("memory-intelligence",)
|
|
35
44
|
|
|
36
45
|
# Wrapper rendered by `wire`. __MI_MCP_BIN__ is replaced with the absolute path
|
|
37
46
|
# to the mi-mcp binary (resolved at wire time) so the host can spawn it even
|
|
@@ -41,15 +50,17 @@ WRAPPER_TEMPLATE = r"""#!/usr/bin/env bash
|
|
|
41
50
|
# Resolves MI_API_KEY at launch so the key never lives in any MCP client config.
|
|
42
51
|
set -euo pipefail
|
|
43
52
|
|
|
44
|
-
# 1. inherited env → 2. macOS Keychain → 3.
|
|
53
|
+
# 1. inherited env → 2. macOS Keychain → 3. keyfile (new then legacy) → fail
|
|
45
54
|
if [[ -z "${MI_API_KEY:-}" ]]; then
|
|
46
55
|
MI_API_KEY="$(security find-generic-password -a "${MI_KEYCHAIN_ACCOUNT:-$USER}" -s "MI_API_KEY" -w 2>/dev/null || true)"
|
|
47
56
|
fi
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
57
|
+
for __mi_envf in "$HOME/.memoryintelligence/.env" "$HOME/.mi-env"; do
|
|
58
|
+
if [[ -z "${MI_API_KEY:-}" && -f "$__mi_envf" ]]; then
|
|
59
|
+
set -a; . "$__mi_envf"; set +a
|
|
60
|
+
fi
|
|
61
|
+
done
|
|
51
62
|
if [[ -z "${MI_API_KEY:-}" ]]; then
|
|
52
|
-
echo "run-mi-mcp: MI_API_KEY not found (env, Keychain service 'MI_API_KEY', or ~/.
|
|
63
|
+
echo "run-mi-mcp: MI_API_KEY not found (env, Keychain service 'MI_API_KEY', or ~/.memoryintelligence/.env)" >&2
|
|
53
64
|
exit 1
|
|
54
65
|
fi
|
|
55
66
|
export MI_API_KEY
|
|
@@ -72,7 +83,7 @@ def _surface_paths(home: Path) -> dict[str, Path]:
|
|
|
72
83
|
|
|
73
84
|
|
|
74
85
|
def _wrapper_path(home: Path) -> Path:
|
|
75
|
-
return home
|
|
86
|
+
return paths.wrapper_path(home=home)
|
|
76
87
|
|
|
77
88
|
|
|
78
89
|
def _mi_mcp_bin() -> str:
|
|
@@ -99,8 +110,10 @@ def _wire_code_via_cli(home: Path, wrapper: Path, dry_run: bool) -> bool:
|
|
|
99
110
|
if dry_run:
|
|
100
111
|
print(f" would run: {' '.join(add_cmd)}")
|
|
101
112
|
return True
|
|
102
|
-
# idempotent: drop any existing entry
|
|
103
|
-
|
|
113
|
+
# idempotent + migrate: drop any existing entry — current id AND the legacy
|
|
114
|
+
# pre-0.1.8 ids — before re-adding, so an upgrade leaves no orphan.
|
|
115
|
+
for key in (SERVER_KEY, *LEGACY_SERVER_KEYS):
|
|
116
|
+
subprocess.run([claude, "mcp", "remove", key], capture_output=True, text=True)
|
|
104
117
|
r = subprocess.run(add_cmd, capture_output=True, text=True)
|
|
105
118
|
if r.returncode != 0:
|
|
106
119
|
print(f" ! claude mcp add failed ({r.stderr.strip()[:160]}); falling back to file")
|
|
@@ -155,13 +168,14 @@ def _resolve_key(home: Path) -> tuple[str, str]:
|
|
|
155
168
|
if r.returncode == 0 and r.stdout.strip():
|
|
156
169
|
return r.stdout.strip(), "keychain"
|
|
157
170
|
except (OSError, subprocess.SubprocessError):
|
|
158
|
-
pass # `security` unavailable/failed — fall through to the
|
|
159
|
-
envf = home
|
|
160
|
-
if envf
|
|
171
|
+
pass # `security` unavailable/failed — fall through to the keyfile
|
|
172
|
+
envf = paths.resolve_keyfile(home) # new ~/.memoryintelligence/.env, else legacy ~/.mi-env
|
|
173
|
+
if envf is not None:
|
|
174
|
+
src = "~/.mi-env" if envf.name == ".mi-env" else "~/.memoryintelligence/.env"
|
|
161
175
|
for line in envf.read_text().splitlines():
|
|
162
176
|
line = line.strip()
|
|
163
177
|
if line.startswith("MI_API_KEY="):
|
|
164
|
-
return line.split("=", 1)[1].strip().strip('"').strip("'"),
|
|
178
|
+
return line.split("=", 1)[1].strip().strip('"').strip("'"), src
|
|
165
179
|
return "", "none"
|
|
166
180
|
|
|
167
181
|
|
|
@@ -175,26 +189,35 @@ def do_wire(home: Path, surfaces: list[str], dry_run: bool) -> None:
|
|
|
175
189
|
Shared by ``cmd_wire`` and ``cmd_setup``. Writes ``env: {}`` only — the key is
|
|
176
190
|
never placed in a config; the wrapper resolves it at launch.
|
|
177
191
|
"""
|
|
178
|
-
|
|
192
|
+
cfg_paths = _surface_paths(home)
|
|
179
193
|
wrapper = _wrapper_path(home)
|
|
180
194
|
bin_path = _mi_mcp_bin()
|
|
181
195
|
entry = {"command": str(wrapper), "args": [], "env": {}}
|
|
182
196
|
|
|
183
|
-
print(f"{'DRY-RUN: ' if dry_run else ''}wiring
|
|
197
|
+
print(f"{'DRY-RUN: ' if dry_run else ''}wiring {SERVER_KEY} MCP server")
|
|
184
198
|
print(f" wrapper → {wrapper}")
|
|
185
199
|
print(f" execs {bin_path}; resolves MI_API_KEY at launch (no key in configs)")
|
|
186
200
|
if not dry_run:
|
|
187
201
|
_atomic_write(wrapper, WRAPPER_TEMPLATE.replace("__MI_MCP_BIN__", bin_path), mode=0o755)
|
|
202
|
+
# Bring a legacy ~/.mi/opt-in-paths forward to the new location (no-op if
|
|
203
|
+
# there's nothing to migrate). Non-destructive — the old file is left.
|
|
204
|
+
if paths.migrate_opt_in_forward(home=home):
|
|
205
|
+
print(f" migrated opt-in allowlist → {paths.opt_in_paths_file(home=home)}")
|
|
188
206
|
|
|
189
207
|
for s in surfaces:
|
|
190
208
|
if s == "code" and _wire_code_via_cli(home, wrapper, dry_run):
|
|
191
209
|
continue
|
|
192
|
-
if s not in
|
|
210
|
+
if s not in cfg_paths:
|
|
193
211
|
print(f" ! unknown surface '{s}' (skipped)")
|
|
194
212
|
continue
|
|
195
|
-
cfg_path =
|
|
213
|
+
cfg_path = cfg_paths[s]
|
|
196
214
|
cfg = _load_json(cfg_path)
|
|
197
215
|
servers = cfg.setdefault("mcpServers", {})
|
|
216
|
+
# Migrate: drop any pre-0.1.8 id (e.g. "memory-intelligence") so the
|
|
217
|
+
# rename to "memoryintelligence" doesn't leave a duplicate/orphan entry.
|
|
218
|
+
migrated = [k for k in LEGACY_SERVER_KEYS if servers.pop(k, None) is not None]
|
|
219
|
+
if migrated:
|
|
220
|
+
print(f" {s:8} migrated id {', '.join(migrated)} → {SERVER_KEY}")
|
|
198
221
|
action = "update" if SERVER_KEY in servers else "add"
|
|
199
222
|
nochange = servers.get(SERVER_KEY) == entry
|
|
200
223
|
servers[SERVER_KEY] = entry
|
|
@@ -219,7 +242,7 @@ def cmd_wire(argv: list[str]) -> int:
|
|
|
219
242
|
do_wire(home, surfaces, args.dry_run)
|
|
220
243
|
if not args.dry_run:
|
|
221
244
|
print("\nNext steps:")
|
|
222
|
-
print(f" 1. opt in a project: echo \"$(pwd)\" >> {home / '
|
|
245
|
+
print(f" 1. opt in a project: echo \"$(pwd)\" >> {paths.mcp_config_dir(home=home) / 'opt-in-paths'}")
|
|
223
246
|
print(" 2. restart Claude (MCP servers load at startup)")
|
|
224
247
|
print(" 3. mi-mcp doctor # verify")
|
|
225
248
|
return 0
|
|
@@ -251,15 +274,20 @@ def cmd_doctor(argv: list[str]) -> int:
|
|
|
251
274
|
check("MI_API_KEY resolvable", bool(key),
|
|
252
275
|
f"source={src}")
|
|
253
276
|
|
|
254
|
-
optin = home
|
|
277
|
+
optin = paths.opt_in_paths_file(home=home)
|
|
255
278
|
n = len(load_opt_in_paths(optin)) if optin.exists() else 0
|
|
256
279
|
check("opt-in allowlist", True,
|
|
257
280
|
f"{n} entries" if optin.exists() else "absent — all captures will skip",
|
|
258
281
|
critical=False)
|
|
259
282
|
|
|
260
283
|
for s, p in _surface_paths(home).items():
|
|
261
|
-
|
|
262
|
-
|
|
284
|
+
servers = _load_json(p).get("mcpServers", {})
|
|
285
|
+
wired = SERVER_KEY in servers
|
|
286
|
+
legacy = [k for k in LEGACY_SERVER_KEYS if k in servers]
|
|
287
|
+
detail = str(p) if wired else "(not wired)"
|
|
288
|
+
if legacy and not wired:
|
|
289
|
+
detail = f"legacy id {', '.join(legacy)} present — run `mi-mcp wire` to migrate"
|
|
290
|
+
check(f"{s} wired", wired, detail, critical=False)
|
|
263
291
|
|
|
264
292
|
print(f"\n {'healthy ✓' if ok else 'issues found ✗'}")
|
|
265
293
|
return 0 if ok else 1
|
|
@@ -276,12 +304,12 @@ def cmd_status(argv: list[str]) -> int:
|
|
|
276
304
|
wired = SERVER_KEY in _load_json(p).get("mcpServers", {})
|
|
277
305
|
print(f" {s:8} {'wired ✓ ' if wired else 'not wired'} {p}")
|
|
278
306
|
|
|
279
|
-
optin = home
|
|
280
|
-
|
|
281
|
-
print(f"\nOpt-in paths ({len(
|
|
282
|
-
for p in
|
|
307
|
+
optin = paths.opt_in_paths_file(home=home)
|
|
308
|
+
opted = load_opt_in_paths(optin) if optin.exists() else []
|
|
309
|
+
print(f"\nOpt-in paths ({len(opted)}):")
|
|
310
|
+
for p in opted:
|
|
283
311
|
print(f" {p}")
|
|
284
|
-
if not
|
|
312
|
+
if not opted:
|
|
285
313
|
print(" (none — captures will be skipped until you add one)")
|
|
286
314
|
return 0
|
|
287
315
|
|
|
@@ -379,13 +407,14 @@ def _store_key_keychain(key: str, account: str) -> None:
|
|
|
379
407
|
|
|
380
408
|
|
|
381
409
|
def _store_key_file(home: Path, key: str) -> Path:
|
|
382
|
-
"""Write the key to a ``chmod 600`` ``~/.
|
|
410
|
+
"""Write the key to a ``chmod 600`` ``~/.memoryintelligence/.env`` keyfile.
|
|
383
411
|
|
|
384
412
|
The cross-platform fallback when there is no Keychain (Linux/Windows) or when
|
|
385
413
|
the user explicitly chooses ``--store file``. Still NOT a config file: the
|
|
386
|
-
launch wrapper sources it at runtime; no MCP config ever holds the key.
|
|
414
|
+
launch wrapper sources it at runtime; no MCP config ever holds the key. (The
|
|
415
|
+
launcher still reads the legacy ``~/.mi-env`` too, for existing installs.)
|
|
387
416
|
"""
|
|
388
|
-
envf = home
|
|
417
|
+
envf = paths.keyfile_path(home)
|
|
389
418
|
content = (
|
|
390
419
|
"# MemoryIntelligence MCP — API key, resolved at launch by run-mi-mcp.sh.\n"
|
|
391
420
|
"# Private (chmod 600). Never commit this file.\n"
|
|
@@ -396,12 +425,12 @@ def _store_key_file(home: Path, key: str) -> Path:
|
|
|
396
425
|
|
|
397
426
|
|
|
398
427
|
def _opt_in_dir(home: Path, directory: str) -> tuple[Path, bool]:
|
|
399
|
-
"""Add ``directory`` (realpath) to ``~/.
|
|
428
|
+
"""Add ``directory`` (realpath) to ``~/.memoryintelligence/mcp/opt-in-paths``.
|
|
400
429
|
|
|
401
430
|
Returns ``(file, added)``. Idempotent — re-running setup in the same dir does
|
|
402
431
|
not duplicate the entry. Realpath normalization matches the consent gate.
|
|
403
432
|
"""
|
|
404
|
-
optin = home / "
|
|
433
|
+
optin = paths.mcp_config_dir(create=True, home=home) / "opt-in-paths"
|
|
405
434
|
target = os.path.realpath(os.path.expanduser(directory))
|
|
406
435
|
existing = load_opt_in_paths(optin) if optin.exists() else []
|
|
407
436
|
if any(os.path.realpath(os.path.expanduser(p)) == target for p in existing):
|
|
@@ -422,7 +451,7 @@ def cmd_setup(argv: list[str]) -> int:
|
|
|
422
451
|
ap.add_argument("--api-key", default=None,
|
|
423
452
|
help="provide the key non-interactively (else you're prompted, hidden)")
|
|
424
453
|
ap.add_argument("--store", choices=["auto", "keychain", "file"], default="auto",
|
|
425
|
-
help="where to keep the key (auto: Keychain on macOS, ~/.
|
|
454
|
+
help="where to keep the key (auto: Keychain on macOS, ~/.memoryintelligence/.env elsewhere)")
|
|
426
455
|
ap.add_argument("--surfaces", default="desktop,code",
|
|
427
456
|
help="comma list of: desktop, code, cursor (default: desktop,code)")
|
|
428
457
|
ap.add_argument("--opt-in", default=None, metavar="DIR",
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""Filesystem layout for Memory Intelligence — one on-brand namespace.
|
|
2
|
+
|
|
3
|
+
Single visible/hidden split, no lookalike names:
|
|
4
|
+
|
|
5
|
+
~/MemoryIntelligence/ visible vault: the user's *.umo files (relocatable via MI_VAULT)
|
|
6
|
+
~/.memoryintelligence/mcp/ hidden MCP config — launcher + capture opt-in allowlist
|
|
7
|
+
~/.memoryintelligence/sdk/ hidden SDK plumbing (models, cache)
|
|
8
|
+
~/.memoryintelligence/.env on-disk keyfile (chmod 600) — cross-platform fallback to the Keychain
|
|
9
|
+
OS Keychain keys, preferred (never on disk)
|
|
10
|
+
|
|
11
|
+
As of 0.1.7 ``mi-mcp setup``/``wire`` write this layout directly. The retired
|
|
12
|
+
``~/.mi/`` config dir and ``~/.mi-env`` keyfile are still *read* (one-release
|
|
13
|
+
fallback) so existing installs keep working until the next ``wire``; nothing is
|
|
14
|
+
moved destructively. The opt-in allowlist is copied forward on ``wire``.
|
|
15
|
+
|
|
16
|
+
Every resolver takes an optional ``home`` (used by the CLI's ``--home`` test
|
|
17
|
+
flag and to keep read/write paths in lockstep); when ``None`` it resolves from
|
|
18
|
+
``$MI_HOME`` then ``~``. This module is the SINGLE source of truth for the
|
|
19
|
+
layout — the CLI and the server's consent gate both call it.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import os
|
|
25
|
+
import shutil
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
|
|
28
|
+
HIDDEN_HOME_ENV = "MI_HOME" # override the hidden plumbing home
|
|
29
|
+
VAULT_ENV = "MI_VAULT" # override the visible vault location
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _home() -> Path:
|
|
33
|
+
return Path(os.path.expanduser("~"))
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _base(home: Path | None) -> Path:
|
|
37
|
+
"""The home under which the layout is rooted. An explicit ``home`` (CLI
|
|
38
|
+
``--home``) wins; else ``~``. ``$MI_HOME`` overrides only the hidden tree."""
|
|
39
|
+
return Path(home) if home is not None else _home()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def hidden_home(home: Path | None = None) -> Path:
|
|
43
|
+
"""``~/.memoryintelligence`` (or ``$MI_HOME``, or ``<home>/.memoryintelligence``)."""
|
|
44
|
+
if home is not None:
|
|
45
|
+
return Path(home) / ".memoryintelligence"
|
|
46
|
+
override = os.environ.get(HIDDEN_HOME_ENV)
|
|
47
|
+
return Path(override).expanduser() if override else _home() / ".memoryintelligence"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def mcp_config_dir(create: bool = False, home: Path | None = None) -> Path:
|
|
51
|
+
d = hidden_home(home) / "mcp"
|
|
52
|
+
if create:
|
|
53
|
+
d.mkdir(parents=True, exist_ok=True)
|
|
54
|
+
return d
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def sdk_dir(create: bool = False, home: Path | None = None) -> Path:
|
|
58
|
+
d = hidden_home(home) / "sdk"
|
|
59
|
+
if create:
|
|
60
|
+
d.mkdir(parents=True, exist_ok=True)
|
|
61
|
+
return d
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def legacy_mcp_dir(home: Path | None = None) -> Path:
|
|
65
|
+
"""The retired ``~/.mi/`` directory (read-only fallback)."""
|
|
66
|
+
return _base(home) / ".mi"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def vault_dir(create: bool = False, home: Path | None = None) -> Path:
|
|
70
|
+
"""The visible ``~/MemoryIntelligence/`` vault (or ``$MI_VAULT``)."""
|
|
71
|
+
override = os.environ.get(VAULT_ENV)
|
|
72
|
+
if override:
|
|
73
|
+
d = Path(override).expanduser()
|
|
74
|
+
else:
|
|
75
|
+
d = _base(home) / "MemoryIntelligence"
|
|
76
|
+
if create:
|
|
77
|
+
d.mkdir(parents=True, exist_ok=True)
|
|
78
|
+
return d
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def wrapper_path(create: bool = False, home: Path | None = None) -> Path:
|
|
82
|
+
"""The launch wrapper (``run-mi-mcp.sh``) that resolves MI_API_KEY at spawn."""
|
|
83
|
+
return mcp_config_dir(create=create, home=home) / "run-mi-mcp.sh"
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def keyfile_path(home: Path | None = None) -> Path:
|
|
87
|
+
"""Preferred on-disk keyfile (``~/.memoryintelligence/.env``, chmod 600) — the
|
|
88
|
+
cross-platform fallback when there is no Keychain. Shared by mcp + sdk."""
|
|
89
|
+
return hidden_home(home) / ".env"
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def legacy_keyfile_path(home: Path | None = None) -> Path:
|
|
93
|
+
"""The retired ``~/.mi-env`` keyfile, still read for back-compat (and the
|
|
94
|
+
ecosystem digest pipeline that points at it)."""
|
|
95
|
+
return _base(home) / ".mi-env"
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def resolve_keyfile(home: Path | None = None) -> Path | None:
|
|
99
|
+
"""Existing keyfile to read — new location preferred, legacy fallback — or
|
|
100
|
+
``None`` if neither exists (then only env/Keychain can supply the key)."""
|
|
101
|
+
for p in (keyfile_path(home), legacy_keyfile_path(home)):
|
|
102
|
+
if p.exists():
|
|
103
|
+
return p
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def opt_in_paths_file(home: Path | None = None) -> Path:
|
|
108
|
+
"""The capture-consent allowlist. New location preferred; falls back to the
|
|
109
|
+
legacy ``~/.mi/opt-in-paths`` if it exists and the new one doesn't."""
|
|
110
|
+
new = mcp_config_dir(home=home) / "opt-in-paths"
|
|
111
|
+
if new.exists():
|
|
112
|
+
return new
|
|
113
|
+
legacy = legacy_mcp_dir(home) / "opt-in-paths"
|
|
114
|
+
if legacy.exists():
|
|
115
|
+
return legacy
|
|
116
|
+
return new # default to the new location for writes
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def migrate_opt_in_forward(home: Path | None = None) -> bool:
|
|
120
|
+
"""Non-destructively copy the legacy opt-in allowlist to the new location if
|
|
121
|
+
the new one doesn't exist yet. Leaves ``~/.mi/`` intact. Returns True if it
|
|
122
|
+
copied."""
|
|
123
|
+
new = mcp_config_dir(home=home) / "opt-in-paths"
|
|
124
|
+
legacy = legacy_mcp_dir(home) / "opt-in-paths"
|
|
125
|
+
if new.exists() or not legacy.exists():
|
|
126
|
+
return False
|
|
127
|
+
new.parent.mkdir(parents=True, exist_ok=True)
|
|
128
|
+
shutil.copy2(legacy, new)
|
|
129
|
+
return True
|
|
@@ -42,7 +42,7 @@ logger = logging.getLogger("mi_mcp")
|
|
|
42
42
|
# surface reduces agent decision-noise; the hidden tools stay callable by name.
|
|
43
43
|
V0_VISIBLE_TOOLS = frozenset({"mi_capture", "mi_ask", "mi_list"})
|
|
44
44
|
|
|
45
|
-
# Write tools — gated by the cwd consent allowlist (~/.
|
|
45
|
+
# Write tools — gated by the cwd consent allowlist (~/.memoryintelligence/mcp/opt-in-paths, Story 8).
|
|
46
46
|
# Read tools are never gated; reading your own memory is always safe.
|
|
47
47
|
WRITE_TOOLS = frozenset({"mi_capture", "mi_batch", "mi_upload"})
|
|
48
48
|
|
|
@@ -139,7 +139,7 @@ def create_server(config: MIConfig | None = None) -> Server:
|
|
|
139
139
|
config = MIConfig.from_env()
|
|
140
140
|
|
|
141
141
|
client = MIClient(config)
|
|
142
|
-
server = Server("
|
|
142
|
+
server = Server("memoryintelligence", instructions=SERVER_INSTRUCTIONS)
|
|
143
143
|
|
|
144
144
|
if os.environ.get("MI_MCP_OPT_IN_ALL") == "1":
|
|
145
145
|
logger.warning(
|
|
@@ -534,7 +534,7 @@ def create_server(config: MIConfig | None = None) -> Server:
|
|
|
534
534
|
logger.info(f"[CONSENT] {name} skipped — cwd not opted in: {cwd}")
|
|
535
535
|
return [TextContent(type="text", text=_fmt({
|
|
536
536
|
"status": "skipped",
|
|
537
|
-
"reason": "cwd not opted in — add it to ~/.
|
|
537
|
+
"reason": "cwd not opted in — run `mi-mcp setup` here, or add it to ~/.memoryintelligence/mcp/opt-in-paths (or set MI_MCP_OPT_IN_ALL=1)",
|
|
538
538
|
"cwd": cwd,
|
|
539
539
|
"tool": name,
|
|
540
540
|
}))]
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
"""Filesystem layout for Memory Intelligence (locked 2026-06-05).
|
|
2
|
-
|
|
3
|
-
One brand namespace, clear visibility split — no lookalike names:
|
|
4
|
-
|
|
5
|
-
~/MemoryIntelligence/ visible vault: the user's *.umo files (relocatable via MI_VAULT)
|
|
6
|
-
~/.memoryintelligence/mcp/ hidden MCP config (opt-in-paths, launcher)
|
|
7
|
-
~/.memoryintelligence/sdk/ hidden SDK plumbing (models, cache)
|
|
8
|
-
OS Keychain keys (never on disk)
|
|
9
|
-
|
|
10
|
-
Retires the legacy ``~/.mi/`` config dir: path resolution prefers the new
|
|
11
|
-
location but falls back to ``~/.mi/`` for one release so existing wiring keeps
|
|
12
|
-
working. Nothing is moved destructively — the launcher relocates on the next
|
|
13
|
-
``mi-mcp wire``; only the opt-in allowlist is (non-destructively) copied forward.
|
|
14
|
-
"""
|
|
15
|
-
|
|
16
|
-
from __future__ import annotations
|
|
17
|
-
|
|
18
|
-
import os
|
|
19
|
-
import shutil
|
|
20
|
-
from pathlib import Path
|
|
21
|
-
|
|
22
|
-
HIDDEN_HOME_ENV = "MI_HOME" # override the hidden plumbing home
|
|
23
|
-
VAULT_ENV = "MI_VAULT" # override the visible vault location
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def _home() -> Path:
|
|
27
|
-
return Path(os.path.expanduser("~"))
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def hidden_home() -> Path:
|
|
31
|
-
"""``~/.memoryintelligence`` (or ``$MI_HOME``)."""
|
|
32
|
-
override = os.environ.get(HIDDEN_HOME_ENV)
|
|
33
|
-
return Path(override).expanduser() if override else _home() / ".memoryintelligence"
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def mcp_config_dir(create: bool = False) -> Path:
|
|
37
|
-
d = hidden_home() / "mcp"
|
|
38
|
-
if create:
|
|
39
|
-
d.mkdir(parents=True, exist_ok=True)
|
|
40
|
-
return d
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def sdk_dir(create: bool = False) -> Path:
|
|
44
|
-
d = hidden_home() / "sdk"
|
|
45
|
-
if create:
|
|
46
|
-
d.mkdir(parents=True, exist_ok=True)
|
|
47
|
-
return d
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def legacy_mcp_dir() -> Path:
|
|
51
|
-
"""The retired ``~/.mi/`` directory."""
|
|
52
|
-
return _home() / ".mi"
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def vault_dir(create: bool = False) -> Path:
|
|
56
|
-
"""The visible ``~/MemoryIntelligence/`` vault (or ``$MI_VAULT``)."""
|
|
57
|
-
override = os.environ.get(VAULT_ENV)
|
|
58
|
-
d = Path(override).expanduser() if override else _home() / "MemoryIntelligence"
|
|
59
|
-
if create:
|
|
60
|
-
d.mkdir(parents=True, exist_ok=True)
|
|
61
|
-
return d
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def opt_in_paths_file() -> Path:
|
|
65
|
-
"""The capture-consent allowlist. New location preferred; falls back to the
|
|
66
|
-
legacy ``~/.mi/opt-in-paths`` if it exists and the new one doesn't."""
|
|
67
|
-
new = mcp_config_dir() / "opt-in-paths"
|
|
68
|
-
if new.exists():
|
|
69
|
-
return new
|
|
70
|
-
legacy = legacy_mcp_dir() / "opt-in-paths"
|
|
71
|
-
if legacy.exists():
|
|
72
|
-
return legacy
|
|
73
|
-
return new # default to the new location for writes
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
def migrate_opt_in_forward() -> bool:
|
|
77
|
-
"""Non-destructively copy the legacy opt-in allowlist to the new location if
|
|
78
|
-
the new one doesn't exist yet. Leaves ``~/.mi/`` intact (the launcher there
|
|
79
|
-
keeps working until the next ``mi-mcp wire``). Returns True if it copied."""
|
|
80
|
-
new = mcp_config_dir() / "opt-in-paths"
|
|
81
|
-
legacy = legacy_mcp_dir() / "opt-in-paths"
|
|
82
|
-
if new.exists() or not legacy.exists():
|
|
83
|
-
return False
|
|
84
|
-
new.parent.mkdir(parents=True, exist_ok=True)
|
|
85
|
-
shutil.copy2(legacy, new)
|
|
86
|
-
return True
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|