Skip to content

feat: add mempalace-mcp entry point for uv compatibility#805

Open
ichoosetoaccept wants to merge 2 commits intoMemPalace:developfrom
detailobsessed:feat/mempalace-mcp-entrypoint
Open

feat: add mempalace-mcp entry point for uv compatibility#805
ichoosetoaccept wants to merge 2 commits intoMemPalace:developfrom
detailobsessed:feat/mempalace-mcp-entrypoint

Conversation

@ichoosetoaccept
Copy link
Copy Markdown

What does this PR do?

Adds support for installing MemPalace with uv, the fast Python package manager that's become the standard (for many) for modern Python tooling.

When MemPalace is installed via uv tool install mempalace, the MCP server and hooks fail because they hardcode python3 -m mempalace.mcp_server — which hits the system Python that doesn't have chromadb or other dependencies. The uv-managed venv has everything, but nothing points to it.

This PR adds a dedicated mempalace-mcp console script entry point and updates all configs and hooks to use it. Both pip install and uv tool install create the same entry point, so this is fully backwards compatible.

Changes

  • New entry point: mempalace-mcp = "mempalace.mcp_server:main" in pyproject.toml
  • MCP configs: .claude-plugin/.mcp.json, .claude-plugin/plugin.json, .codex-plugin/plugin.json now use mempalace-mcp instead of python3 -m mempalace.mcp_server
  • Hook scripts: All 5 hooks (claude-plugin, codex-plugin, hooks/) now use mempalace CLI entry point instead of python3 -m mempalace
  • CLI help: mempalace mcp now prints mempalace-mcp as the setup command
  • README: Added uv tool install mempalace as recommended install method
  • Tests: New test_mcp_entrypoint.py verifying entry point, configs, and hooks don't regress

What stays the same

  • python -m mempalace.mcp_server still works (for existing setups)
  • python3 -c calls in legacy hooks untouched (stdlib-only JSON parsing, no mempalace imports)
  • No new CLI subcommands — mempalace-mcp is the MCP server entry point

How to test

uv tool install .
which mempalace-mcp        # should resolve
mempalace-mcp              # starts MCP server (JSON-RPC on stdin)
mempalace mcp              # prints updated setup instructions
python -m pytest tests/ -v # 699 tests pass

Checklist

  • Tests pass (python -m pytest tests/ -v)
  • No hardcoded paths
  • Linter passes (ruff check .)

@igorls igorls added area/cli CLI commands area/hooks Claude Code hook scripts (Stop, PreCompact, SessionStart) area/install pip/uv/pipx/plugin install and packaging area/mcp MCP server and tools enhancement New feature or request labels Apr 14, 2026
@ichoosetoaccept ichoosetoaccept force-pushed the feat/mempalace-mcp-entrypoint branch from d839f2a to e2a9e79 Compare April 14, 2026 12:21
rosschurchill added a commit to rosschurchill/mempalace that referenced this pull request Apr 18, 2026
…1 integrity bundle

Phase 6 pays the debt surfaced by the advisor review of Phases 0–5:
one regression we introduced, four "already fixed" claims we never
verified, deferred quick wins, and the P1 MemPalace#934 data-loss bundle.

No new features. 1024 tests passing, all lint clean.

── Part A: Regressions and empirical verification ──

A1. HTTP-mode embedding guard regression (REAL BUG WE INTRODUCED):
  Phase 5 made write_palace_meta() a no-op in HTTP mode so
  palace_meta.json never got written — the Phase 2 model-drift warning
  was silently defeated in the multi-client scenario where drift is most
  likely. Fix: store embedding_model in ChromaDB's collection.metadata
  (works in both HTTP and embedded modes), keep palace_meta.json as a
  local cache for embedded-only callers. Added set_metadata() to the
  ChromaCollection adapter that strips hnsw:* keys (immutable by design).

A1b. Settings mutation bug (found while fixing A1):
  _CHROMA_SETTINGS was a shared module-level Settings object. ChromaDB
  mutates it during HttpClient init by writing chroma_server_host/port
  into it — a subsequent PersistentClient using the SAME object would
  then silently switch to HTTP and fail with "Could not connect to a
  Chroma server" even when MEMPALACE_CHROMA_URL was unset. Replaced
  with _chroma_settings() factory that returns a fresh Settings per call.

A2. Verification tests (tests/test_verification.py, 10 new tests):
  Each claim in the Phase 1–5 commits is now backed by a real test:
    MemPalace#590 — JSONL tool_result content preserved (unique marker survives)
    MemPalace#608 — Second backend sees first backend's writes (cache invalidates)
    MemPalace#655 — add_triple, evolve_fact, REM bridges all dedup correctly
    MemPalace#816 — MCP handles tools/call with extra Gemini-style fields
    MemPalace#903 — HTTP-mode embedding guard fires on mismatch, silent on match

── Part B: Deferred quick wins ──

B1. MemPalace#847 — `mempalace status --palace /x` now works.
  Previous code only accepted --palace BEFORE the subcommand. Added
  per-subcommand --palace via parents=[palace_parent] and a merge step
  that falls back to the top-level arg. Both positions work now.

B3. MemPalace#805 — mempalace-mcp entry point added to pyproject.toml.
  Enables `uvx mempalace-mcp` installation.

B4. PreCompact timeout logging (MemPalace#906/MemPalace#941/MemPalace#949 root cause):
  _mine_sync() caught subprocess.TimeoutExpired silently — masking data
  loss as success. Now logs explicitly with transcript path and duration
  so hook.log shows exactly when mining failed to complete.

B5. REM cycle per-query timeout guard:
  col.query() calls now run in a ThreadPoolExecutor with 10s timeout.
  A single stuck query no longer stalls the entire scan. Added
  queries_timed_out counter to the result.

B6. _get_all_wings 30s TTL cache:
  Was paging through the entire palace on every explain() call (O(N)).
  Now cached keyed by id(col) with 30s TTL.

── Part C: MemPalace#934 P1 data-integrity bundle (all 7 items) ──

C1. repair.py — upsert loop wrapped in try/except, restores from
    chroma.sqlite3.backup on any failure. No more partial-rebuild data loss.

C2. migrate.py — atomic palace swap via os.rename(palace → .old) +
    os.rename(temp → palace). Crash at any point leaves one intact.

C3. cli.py cmd_repair — offset += len(batch["ids"]) not batch_size,
    and break on empty batch. No more silently-skipped drawers.

C4. mcp_server.py tool_diary_write — topic now passes through
    sanitize_name(), rejecting path traversal/null bytes/overlong strings.

C5. closet_llm.py — JSONDecodeError now retries (with backoff) up to
    3 attempts. LLM non-determinism no longer permanently skips files.

C6. mcp_server.py tool_reconnect — now resets _metadata_cache and
    _metadata_cache_time (was leaking stale metadata for 5s TTL).

C7. exporter.py — refuses symlinked output_dir or wing dirs. Prevents
    attackers pre-seeding symlinks to redirect export data.

── Part D/E: New issue cleanup + UX docs ──

D1. MemPalace#923 — oversized files (>10MB) now logged with size + limit.
    Users can see exactly why an expected file wasn't mined.

D3. MemPalace#929 — verified: non-ASCII diary round-trip works correctly after
    our Phase 0 ensure_ascii=False fix. Test covers Chinese + arrows
    + stars + check/cross chars.

E1. docs/RETRIEVAL_TOOLS.md — one-page decision guide for search vs
    explain vs whisper. Addresses the UX regression from having 3
    overlapping "find stuff" tools.

── Tests ──

16 new tests across test_verification.py (10) and test_phase6_integrity.py (6).
Total: 1024 passing, 1 pre-existing env-related failure (unchanged).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/cli CLI commands area/hooks Claude Code hook scripts (Stop, PreCompact, SessionStart) area/install pip/uv/pipx/plugin install and packaging area/mcp MCP server and tools enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants