diff --git a/README.md b/README.md index 5ff8563..c3540e5 100644 --- a/README.md +++ b/README.md @@ -585,6 +585,9 @@ mempalace compress --wing myapp # AAAK compress # Status mempalace status # palace overview + +# MCP +mempalace mcp # show MCP setup command ``` All commands accept `--palace ` to override the default location. diff --git a/mempalace/cli.py b/mempalace/cli.py index 895aa87..d8dc697 100644 --- a/mempalace/cli.py +++ b/mempalace/cli.py @@ -14,6 +14,7 @@ Commands: mempalace mine Mine project files (default) mempalace mine --mode convos Mine conversation exports mempalace search "query" Find anything, exact words + mempalace mcp Show MCP setup command mempalace wake-up Show L0 + L1 wake-up context mempalace wake-up --wing my_app Wake-up for a specific project mempalace status Show what's been filed @@ -28,6 +29,7 @@ Examples: import os import sys +import shlex import argparse from pathlib import Path @@ -241,6 +243,27 @@ def cmd_instructions(args): run_instructions(name=args.name) +def cmd_mcp(args): + """Show how to wire MemPalace into MCP-capable hosts.""" + base_server_cmd = "python -m mempalace.mcp_server" + + if args.palace: + resolved_palace = str(Path(args.palace).expanduser()) + server_cmd = f"{base_server_cmd} --palace {shlex.quote(resolved_palace)}" + else: + server_cmd = base_server_cmd + + print("MemPalace MCP quick setup:") + print(f" claude mcp add mempalace -- {server_cmd}") + print("\nRun the server directly:") + print(f" {server_cmd}") + + if not args.palace: + print("\nOptional custom palace:") + print(f" claude mcp add mempalace -- {base_server_cmd} --palace /path/to/palace") + print(f" {base_server_cmd} --palace /path/to/palace") + + def cmd_compress(args): """Compress drawers in a wing using AAAK Dialect.""" import chromadb @@ -501,6 +524,12 @@ def main(): help="Rebuild palace vector index from stored data (fixes segfaults after corruption)", ) + # mcp + sub.add_parser( + "mcp", + help="Show MCP setup command for connecting MemPalace to your AI client", + ) + # status sub.add_parser("status", help="Show what's been filed") @@ -532,6 +561,7 @@ def main(): "mine": cmd_mine, "split": cmd_split, "search": cmd_search, + "mcp": cmd_mcp, "compress": cmd_compress, "wake-up": cmd_wakeup, "repair": cmd_repair, diff --git a/mempalace/instructions/help.md b/mempalace/instructions/help.md index f18c1de..5cb70fa 100644 --- a/mempalace/instructions/help.md +++ b/mempalace/instructions/help.md @@ -60,6 +60,7 @@ AI memory system. Store everything, find anything. Local, free, no API key. mempalace compress Compress palace storage mempalace status Show palace status mempalace repair Rebuild vector index + mempalace mcp Show MCP setup command mempalace hook run Run hook logic (for harness integration) mempalace instructions Output skill instructions diff --git a/tests/test_cli.py b/tests/test_cli.py index c43079f..e3c68f9 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -2,6 +2,7 @@ import argparse import sys +from pathlib import Path from unittest.mock import MagicMock, patch import pytest @@ -326,6 +327,35 @@ def test_main_split_dispatches(): mock_cmd.assert_called_once() +def test_mcp_command_prints_setup_guidance(monkeypatch, capsys): + monkeypatch.setattr(sys, "argv", ["mempalace", "mcp"]) + + main() + + captured = capsys.readouterr() + assert "MemPalace MCP quick setup:" in captured.out + assert "claude mcp add mempalace -- python -m mempalace.mcp_server" in captured.out + assert "\nOptional custom palace:\n" in captured.out + assert "python -m mempalace.mcp_server --palace /path/to/palace" in captured.out + assert "[--palace /path/to/palace]" not in captured.out + assert captured.err == "" + + +def test_mcp_command_uses_custom_palace_path_when_provided(monkeypatch, capsys): + monkeypatch.setattr(sys, "argv", ["mempalace", "--palace", "~/tmp/my palace", "mcp"]) + + main() + + captured = capsys.readouterr() + expanded = str(Path("~/tmp/my palace").expanduser()) + + assert "python -m mempalace.mcp_server --palace" in captured.out + assert expanded in captured.out + assert "Optional custom palace:" not in captured.out + assert "[--palace /path/to/palace]" not in captured.out + assert captured.err == "" + + def test_main_hook_no_subcommand_prints_help(capsys): with patch("sys.argv", ["mempalace", "hook"]): main()