Skip to content

[bug]: memtomem-server is POSIX-only — fcntl→portalocker swap to enable Windows MCP host #817

@memtomem

Description

@memtomem

Summary

memtomem-server (the MCP server, exposed as the memtomem-server / python -m memtomem.server entry point) is POSIX-only by construction. The two fcntl.flock callsites in packages/memtomem/src/memtomem/server/__init__.py (lines 280 and 338) sit inside lazy import fcntl blocks, so the mm CLI loads cleanly on Windows post-#652, but memtomem-server itself crashes the moment main() reaches the legacy-flock probe or the XDG pid lock.

This was an explicit follow-up promised on #625:

server/__init__.py keeps fcntl for now — those callsites are already lazy and the server itself is POSIX-only by design (no SIGTERM, different pid semantics). If a Windows server target ever lands, that becomes its own follow-up.

A Windows server target is now needed: users are running Claude Desktop / Code on Windows boxes and want to point an MCP host at memtomem-server directly without going through WSL.

Surface

memtomem-server (MCP server)

Steps to reproduce

  1. On a Windows host post-feat(locking): swap fcntl for portalocker (closes #625) #652 (so mm itself loads): memtomem-server (or python -m memtomem.server)
  2. Server crashes inside main() when it reaches fcntl.flock(...).

Expected behavior

memtomem-server starts on Windows, holds a real exclusive pid lock at %LOCALAPPDATA%\Temp\memtomem-0\server.pid, serves MCP over stdio, and the pid file is unlinked at shutdown via atexit (Windows has no SIGTERM equivalent for graceful shutdown — stdin-EOF + atexit is the only path).

Actual behavior

ModuleNotFoundError: No module named 'fcntl' raised from inside main().

Proposed fix (scope sketch)

  • Swap fcntl.flockportalocker.lock at the two server callsites, mirroring feat(locking): swap fcntl for portalocker (closes #625) #652's swap in _atomic.py, _liveness.py, and debounce.py.
  • Short-circuit _try_hold_legacy_flock on Windows (os.name == "nt" → return None). pre-0.1.25 servers were Linux-only by definition (the CLI didn't load on Windows until feat(locking): swap fcntl for portalocker (closes #625) #652 / 0.1.34), so the cross-version mutex it implements has no Windows-side counterparty to interlock with — and skipping it sidesteps the question of whether portalocker's Windows backend (MsvcrtLocker vs Win32Locker) implements LOCK_SH semantics uniformly.
  • POSIX-gate the SIGTERM handler registration in _install_sigterm_handler (Python's signal.signal(SIGTERM, ...) is a no-op on Windows).
  • Test surface adjustments in test_server_sigterm.py and test_uninstall_cmd.py: keep SIGTERM-firing tests POSIX-skipped, convert the contended-flock simulator to portalocker so it runs on Windows too, unskip the tempdir-fallback test (with os.geteuid()/mode-bit guards), and add a new test_server_main_acquires_portalocker_pid_lock regression pin that runs identically on every OS.

Out of scope for this issue (separate follow-ups if needed):

  • Windows ODR registration via MSIX manifest / MCPB bundle / odr.exe. That's packaging, not server code.
  • Flipping the CI Windows job from continue-on-error: true to required (gated on the broader Windows-triage umbrella ci: add Windows runner to CI matrix #634).

memtomem version

0.1.35

Operating system

Windows

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions