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
29 changes: 29 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,34 @@
# Changelog

## [Unreleased]

### Fixed
- **``tests/test_cli_help.py:_run_cli`` helper preferred the ASR-blocked
shim** — the pre-fix helper called ``shutil.which("truememory-mcp")``
and used the bare ``.exe`` shim when present, falling back to
``python -m`` only if the shim wasn't on PATH. On Windows hosts with
Microsoft Defender ASR rule ``01443614`` ("Block executable files
from running unless they meet a prevalence, age, or trusted list
criteria") in **Block** mode, the shim is silently killed at launch
— making 7 of the 9 tests in this file fail with
``PermissionError [WinError 5] Access is denied`` even on a healthy
install. Rewrote the helper to always invoke
``[sys.executable, "-m", "truememory.mcp_server"]``, routing through
the signed ``python.exe`` wrapper. Sibling fix in PR #351 covered
the 2 remaining tests in this file that bypass the helper entirely.
- **``mcp_server.py`` `__main__` block dropped exit codes under
``python -m`` invocation** — the bottom-of-file
``if __name__ == "__main__": main()`` discarded ``main()``'s return
value, so exit-code-2 paths (unknown flag, positional-arg typo)
silently exited 0 when the server was invoked via
``python -m truememory.mcp_server``. The setuptools console-script
wrapper around ``truememory-mcp`` already does ``sys.exit(main())``,
so the bug was invisible until anything routed through ``-m``.
Changed to ``sys.exit(main() or 0)``. Surfaced by the ``_run_cli``
helper rewrite above — 2 tests asserting ``returncode != 0`` were
passing under the shim and silently regressed to passing-as-0 under
``-m`` until this line landed.

## [0.6.8] — 2026-05-11

### Fixed
Expand Down
34 changes: 18 additions & 16 deletions tests/test_cli_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,25 @@
from truememory import __version__


def _truememory_mcp_bin() -> str | None:
"""Locate the installed truememory-mcp console script, or None.

Prefer the script installed by pip. Fall back to invoking via
`python -m truememory.mcp_server` — slower because it re-runs all
module-level imports, but works in any environment where truememory
is importable.
"""
return shutil.which("truememory-mcp")


def _run_cli(args: list[str], timeout: int = 30) -> subprocess.CompletedProcess:
bin_path = _truememory_mcp_bin()
if bin_path:
cmd = [bin_path] + args
else:
cmd = [sys.executable, "-m", "truememory.mcp_server"] + args
"""Run the truememory-mcp CLI via the module form.

Always invokes ``[sys.executable, "-m", "truememory.mcp_server"]``
rather than the bare ``truememory-mcp`` console-script shim. The
shim path is faster (cached imports) but is a setuptools / uv
trampoline with a per-install unique hash — Windows Defender's
Attack-Surface-Reduction rule ``01443614`` ("Block executable files
from running unless they meet a prevalence, age, or trusted list
criteria") silently kills it at launch on hardened Win11 baselines.
Routing through the signed ``python.exe`` wrapper passes ASR
everywhere; the per-test re-import cost is negligible at test scale.

Replaces the pre-fix two-arm helper that called ``shutil.which`` and
preferred the shim when present — that branch fired exactly on
Block-mode ASR boxes, making 7 of the tests in this file fail with
``PermissionError [WinError 5] Access is denied``.
"""
cmd = [sys.executable, "-m", "truememory.mcp_server"] + args
return subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)


Expand Down
10 changes: 9 additions & 1 deletion truememory/mcp_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -1519,4 +1519,12 @@ def main():


if __name__ == "__main__":
main()
# `sys.exit(main())` rather than bare `main()` — so exit codes
# returned from `main()` propagate when invoked via
# ``python -m truememory.mcp_server``. The setuptools console-script
# wrapper around `truememory-mcp` already does `sys.exit(main())`
# for you; `-m` invocation does not, so the bare `main()` form
# silently masked exit-code-2 paths (unknown flag, positional arg
# typo) as exit 0 under `python -m`.
import sys as _sys
_sys.exit(main() or 0)