Skip to content

feat: new MCP tools — get/list/update drawer, hook settings, export (resolves #635)#667

Merged
bensig merged 7 commits intodevelopfrom
merge/635-resolved
Apr 12, 2026
Merged

feat: new MCP tools — get/list/update drawer, hook settings, export (resolves #635)#667
bensig merged 7 commits intodevelopfrom
merge/635-resolved

Conversation

@bensig
Copy link
Copy Markdown
Collaborator

@bensig bensig commented Apr 12, 2026

Summary

Merge-conflict resolution of #635 by @jphein against develop (after #647 landed).

All code is from #635 — conflicts in mcp_server.py resolved:

Fixes #646 (0 drawers on claude.ai export) via paginated exporter.py.

Original PR: #635
Original author: @jphein — all feature code in this PR is their work.

Co-authored-by: jphein [email protected]

Test plan

  • CI passes
  • mempalace export produces correct output on large palaces

jphein and others added 6 commits April 11, 2026 19:47
…, search limits

Infrastructure hardening for the MCP server:
- Detect palace DB replacement via inode tracking (repair command support)
- WAL rotation to prevent unbounded WAL growth
- _fetch_all_metadata() + _get_cached_metadata() with 60s TTL for taxonomy/status
- _MAX_RESULTS cap (100) with limit clamping [1, _MAX_RESULTS]
- max_distance parameter for similarity threshold in search
- Handle all notifications/* methods, null arguments, method=None
- Remove duplicate _client_cache = None declarations
- searcher.py max_distance parameter passthrough

Co-Authored-By: Claude Opus 4.6 <[email protected]>
…filed), export, normalize

New MCP tools:
- mempalace_get_drawer: fetch single drawer by ID with full content
- mempalace_list_drawers: paginated listing with wing/room filter
- mempalace_update_drawer: update content/wing/room on existing drawers
- mempalace_hook_settings: get/set hook behavior (silent_save, desktop_toast)
- mempalace_memories_filed_away: check latest checkpoint status

Also includes:
- exporter.py: export palace as browsable markdown files
- normalize.py: tool_use/tool_result capture for richer transcript mining
- layers.py: updated for new tool integration
- config.py: hook settings properties (hook_silent_save, hook_desktop_toast)

Depends on PR 3 (reliability) for _MAX_RESULTS, _metadata_cache, WAL logging.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
- Handle explicit null in MCP params (request.get("params") or {})
- Fix search tool description: L2 → cosine distance (collection uses hnsw:space=cosine)
- Guard against empty string key in tool_use_map from malformed JSONL entries

Co-Authored-By: Claude Opus 4.6 <[email protected]>
- WAL write: keep os.open atomic approach from #647
- tool_search: keep _MAX_RESULTS + min_similarity compat from #635
- schema_props filter: remove duplicate (already present above conflict)
@bensig
Copy link
Copy Markdown
Collaborator Author

bensig commented Apr 12, 2026

Code review

Found 3 issues:

  1. min_similarity backwards-compat shim passes raw similarity value as max_distance, but these are opposite scales (higher similarity = stricter, lower distance = stricter). Passing min_similarity=0.8 results in max_distance=0.8 which is permissive, not strict. Should convert: dist = (1 - min_similarity) if min_similarity is not None else max_distance.

"limit": limit,
}
except Exception as e:

  1. tool_status, tool_list_wings, tool_list_rooms, and tool_get_taxonomy replace structured error reporting ({"error": str(e), "partial": True}) with bare except: pass. This directly reverts the error surfacing added in the recently merged Security hardening: input validation, argument whitelisting, concurrency & WAL fixes #647 security hardening. Callers now get empty/incomplete results with no signal something went wrong.

try:
wing = sanitize_name(wing, "wing")
room = sanitize_name(room, "room")
content = sanitize_content(content)
except ValueError as e:
return {"success": False, "error": str(e)}
col = _get_collection(create=True)
if not col:

  1. _get_client inode cache: when os.stat fails (file missing), current_inode = 0. The condition current_inode and current_inode != _palace_db_inode short-circuits on falsy 0, so a missing/deleted database silently reuses the stale cached client instead of reconnecting.

pass
return {"taxonomy": taxonomy}
def tool_search(

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

1. min_similarity backwards-compat: convert similarity to distance scale
   (1.0 - similarity) instead of passing raw value as max_distance
2. Restore structured error reporting (error + partial fields) in
   tool_status, tool_list_wings, tool_list_rooms, tool_get_taxonomy
   — reverts silent except:pass that dropped #647 security hardening
3. inode cache: remove falsy-zero short-circuit so missing DB file
   triggers reconnect instead of reusing stale client
4. _fetch_all_metadata: check for empty batch before extending/advancing
   offset to prevent infinite loop on concurrent deletion
5. KG initialization: only override path when --palace is explicit;
   default runs use KnowledgeGraph's built-in default path

Co-authored-by: jphein <[email protected]>
@bensig bensig merged commit 20c8f8e into develop Apr 12, 2026
jphein added a commit to jphein/mempalace that referenced this pull request Apr 12, 2026
…_path

KnowledgeGraph() uses its own default db_path which may differ from
config.palace_path when --palace isn't specified. Only override when
the user explicitly provides --palace.

Per review feedback from bensig in MemPalace#667.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@jphein
Copy link
Copy Markdown
Collaborator

jphein commented Apr 12, 2026

Great catches, all 5. I've incorporated every fix into my fork:

  1. min_similarity conversion — embarrassing but fair, 1.0 - similarity is obviously correct for cosine distance. I only ever used max_distance directly so this never bit me in production.
  2. Structured error reporting — your result["partial"] = True pattern is much better than the bare except: pass I had. Already adopted it across my fork.
  3. Inode zero guard — subtle one. The current_inode and ... short-circuit was masking the missing-DB case. My fix: detect mtime changes in _get_collection to prevent stale HNSW index #663 (stale HNSW) PR had an explicit os.path.isfile() guard that worked around it, but removing the falsy check is the right fix at the root.
  4. Pagination guard — classic TOCTOU race. Moving the empty check before extend prevents the infinite loop on concurrent deletion.
  5. KG init — reverted in my fix: standalone bug fixes (#534 #535 #536 #538 #570 #572 #637) #626 too. KnowledgeGraph() knows its own default path.

Thanks for the thorough review — learned something on each one.

jphein added a commit to jphein/mempalace that referenced this pull request Apr 12, 2026
…t count 701

- README: mark MemPalace#635 as merged via MemPalace#667, fix cosine (not L2) distance
- CLAUDE.md: test count 692→701, drawer count 132K→134K, replace
  stale upstream PR list with current table, add fork change MemPalace#22
  (bensig's security/pagination/error reporting fixes from MemPalace#667)

Co-Authored-By: Claude Opus 4.6 <[email protected]>
skuznetsov pushed a commit to skuznetsov/mempalace that referenced this pull request Apr 12, 2026
Merge origin/develop into codex/pg-backend-stage2 after upstream PRs MemPalace#647 and MemPalace#667 landed.

Resolve the MCP server conflict by preserving the new cached metadata helpers, WAL hardening, drawer update/list tools, and exporter changes from develop while keeping the PostgreSQL backend abstraction: MCP collection access now goes through palace.get_collection(...) instead of constructing a Chroma PersistentClient directly.

Keep the large-table optimization for PostgreSQL: metadata pagination no longer performs an exact col.count() pre-pass, MCP status derives total_drawers from fetched metadata rows, and MemoryStack.status() can use backend-provided estimated_count() so PostgreSQL can use catalog/statistics estimates rather than SELECT COUNT(*) on status paths.

Also make tool_update_drawer backend-neutral by using upsert() instead of the Chroma-only update() method, with tests covering the estimated count path.

Verification: uv run pytest -q (671 passed, 106 deselected); uv run ruff check .; git diff --check; git diff --cached --check; precise conflict-marker scan.
dekoza added a commit to dekoza/mempalace that referenced this pull request Apr 12, 2026
Required by upstream's tool_update_drawer (PR MemPalace#667). LanceCollection
implements update as get+upsert (re-embeds on content change).
ChromaCollection delegates to chromadb's native update.
jphein added a commit to jphein/mempalace that referenced this pull request Apr 12, 2026
- Add 5 missing tools: get_drawer, list_drawers, update_drawer,
  hook_settings, memories_filed_away (shipped in MemPalace#667)
- Add max_distance and context params to mempalace_search
- Add distance field to search response examples
- Update tool count from 19 to 24

Co-Authored-By: Claude Opus 4.6 <[email protected]>
jphein added a commit to jphein/mempalace that referenced this pull request Apr 12, 2026
- Update 19 → 24 in four places (intro, heading, file table, tree)
- Add get_drawer, list_drawers, update_drawer to Palace (write) table
- Add Settings section with hook_settings and memories_filed_away

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@igorls igorls mentioned this pull request Apr 13, 2026
4 tasks
jphein added a commit to jphein/mempalace that referenced this pull request Apr 13, 2026
- Add 5 missing tools: get_drawer, list_drawers, update_drawer,
  hook_settings, memories_filed_away (shipped in MemPalace#667)
- Add max_distance and context params to mempalace_search
- Add distance field to search response examples
- Update tool count from 19 to 24

Co-Authored-By: Claude Opus 4.6 <[email protected]>
jphein added a commit to jphein/mempalace that referenced this pull request Apr 13, 2026
- Update 19 → 24 in four places (intro, heading, file table, tree)
- Add get_drawer, list_drawers, update_drawer to Palace (write) table
- Add Settings section with hook_settings and memories_filed_away

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@igorls igorls deleted the merge/635-resolved branch April 13, 2026 22:37
jphein added a commit to jphein/mempalace that referenced this pull request Apr 19, 2026
Remove three stale rows that are already in upstream/develop:
- Epsilon mtime comparison (merged upstream as PR MemPalace#610, Arnold Wender)
- .jsonl READABLE_EXTENSIONS addition (upstream-authored at SHA 560fdbd)
- max_distance threshold (superset already in upstream via MemPalace#667)

All three moved to "Superseded by upstream" with attribution.

Add two rows that were fork-ahead but missing from the table:
- cmd_export and cmd_purge CLI commands (cli.py, PR pending)
- mempal_precompact_hook.sh transcript auto-mining: session ID parsing,
  find-by-session-id fallback, inline chunk_exchanges → upsert (PR pending)

Annotate existing rows:
- PID guard: branch pr/pid-file-guard (local, not pushed)
- Save hook Python auto-detection: expand files list to include both
  .claude-plugin/ hooks (same pattern was applied there)

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: _try_claude_ai_json parser silently produces 0 drawers on claude.ai export (conversations.json)

3 participants