Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,26 @@ jobs:
case "$out" in
*Traceback*) echo "::error::base install printed a traceback"; exit 1 ;;
esac
- name: the installed server must find an index relative to the user's directory
# The tests run from the source tree, and the steps above only check that
# the commands start. Neither reproduces how people actually launch the MCP
# server: from their OWN directory, with a relative BRAIN_INDEX_DIR, against
# the installed wheel. The recall child used to run with cwd set to the
# package directory inside site-packages, so "index" resolved there and the
# user's index was never found. CI stayed green on exactly that bug, so this
# step reproduces it: a minimal index in /tmp/userdir, launch from there.
# Index found => brain_ask gets past loading and stops at the missing
# [embeddings] extra. Index NOT found => "need an embedding index".
run: |
mkdir -p /tmp/userdir/index
cd /tmp/userdir
/tmp/clean/bin/python -c "import numpy as np, pickle; np.save('index/_brain_e5.npy', np.zeros((1, 4), dtype='float32')); pickle.dump([{'path': 'a.md', 'chunk_idx': 0}], open('index/_brain_e5_meta.pkl', 'wb'))"
out=$(env -u PYTHONPATH BRAIN_INDEX_DIR=index /tmp/clean/bin/sgm-mcp --test "agent memory" 2>&1 || true)
echo "$out"
case "$out" in
*"need an embedding index"*) echo "::error::installed server looked for the index outside the user's directory"; exit 1 ;;
esac
case "$out" in
*"sqlite-graph-memory[embeddings]"*) echo "index found from the user's directory" ;;
*) echo "::error::unexpected output from the installed server"; exit 1 ;;
esac
1 change: 0 additions & 1 deletion src/sqlite_graph_memory/mcp_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,6 @@ def run_recall(query: str, mode: str = "associative") -> tuple[str, bool]:
call_env["PYTHONPATH"] = f"{src_dir}{os.pathsep}{existing_pp}" if existing_pp else src_dir
res = subprocess.run(
cmd,
cwd=str(ROOT_DIR),
env=call_env,
capture_output=True,
text=True,
Expand Down
59 changes: 59 additions & 0 deletions tests/test_mcp_script_mode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
"""Script-mode and working-directory behaviour of the MCP server.

Kept in its own file so it does not collide with edits to test_mcp_server.py.
Runs offline: no model, no index, no network.
"""
import json
import os
import subprocess
import sys
from pathlib import Path

SRC = Path(__file__).resolve().parents[1] / "src"
sys.path.insert(0, str(SRC))
from sqlite_graph_memory import __version__ # noqa: E402
from sqlite_graph_memory import mcp_server # noqa: E402

SCRIPT = SRC / "sqlite_graph_memory" / "mcp_server.py"


def test_script_mode_starts_without_installed_package(tmp_path):
"""`python src/sqlite_graph_memory/mcp_server.py` must not need the package on sys.path.

Python puts the script's own directory (not src/) on sys.path, so an
absolute `from sqlite_graph_memory import ...` at import time crashed with
ModuleNotFoundError before the server answered anything (issue #24).
`-S` drops site-packages so an installed or editable copy cannot mask it.
"""
env = {k: v for k, v in os.environ.items() if k != "PYTHONPATH"}
req = {"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {}}
res = subprocess.run(
[sys.executable, "-S", str(SCRIPT)],
input=json.dumps(req) + "\n",
capture_output=True, text=True, cwd=str(tmp_path), env=env, timeout=30,
)
assert res.returncode == 0, res.stderr
resp = json.loads(res.stdout.strip().splitlines()[-1])
assert resp["result"]["serverInfo"]["version"] == __version__


def test_recall_subprocess_inherits_server_cwd(tmp_path, monkeypatch):
"""Relative BRAIN_INDEX_DIR / TURNSTATE_DB must resolve against the user's cwd.

On an installed wheel the package dir is site-packages/sqlite_graph_memory;
running brain_ask there made `BRAIN_INDEX_DIR=index` miss the user's ./index.
"""
work = tmp_path / "work"
work.mkdir()
stub = tmp_path / "stub_brain_ask.py"
stub.write_text(
"import os, pathlib\n"
"pathlib.Path(os.environ['BRAIN_ANSWER_OUT']).write_text(os.getcwd(), encoding='utf-8')\n",
encoding="utf-8",
)
monkeypatch.setattr(mcp_server, "_brain_ask_cmd", lambda: [sys.executable, str(stub)])
monkeypatch.chdir(work)
text, is_error = mcp_server.run_recall("anything")
assert is_error is False, text
assert Path(text).resolve() == work.resolve()
Loading