From 65fb69c0af16ff2dd4012e98edfcd3f852f8e3c1 Mon Sep 17 00:00:00 2001 From: CPU <156097+caseypugh@users.noreply.github.com> Date: Sat, 6 Jun 2026 11:24:37 -0700 Subject: [PATCH] fix: resolve session dir to an absolute writable path The session directory was created at a relative `.mm` path, so it landed in whatever working directory the MCP client launched the server from. Clients like Claude Desktop launch with a read-only cwd (e.g. `/`), which crashes startup before the server can connect: OSError: [Errno 30] Read-only file system: '.mm' Resolve the session dir to an absolute path instead: honor a new MONARCH_SESSION_DIR env var, otherwise default to ~/.monarch-mcp (always writable regardless of launch cwd). Use parents=True on mkdir and update the docs that referenced the old `.mm/` location. Co-Authored-By: Claude Opus 4.8 (1M context) --- CLAUDE.md | 8 ++++---- README.md | 6 +++--- server.py | 11 ++++++++--- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 5d3b64e..ace32ee 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -30,7 +30,7 @@ Use generic, obviously-fake examples instead: "Main Credit Card", "Corner Deli", - `MONARCH_FORCE_LOGIN=true uv run python server.py` - Force fresh login (if session expires) ### Debugging Startup Issues (Updated July 2025) -- **Session expired**: Delete `.mm/session.pickle` or set `MONARCH_FORCE_LOGIN=true` +- **Session expired**: Delete `~/.monarch-mcp/session.pickle` or set `MONARCH_FORCE_LOGIN=true` - **JSON parse errors**: Fixed - all stdout output suppressed with `contextlib.redirect_stdout()` - **MCP protocol compliance**: All logging/warnings redirected to stderr, third-party lib output suppressed - **AsyncIO errors**: Fixed - uses `run_stdio_async()` in async context @@ -166,7 +166,7 @@ refactor: split server.py into modular components (auth, tools, models) - Automatic capability negotiation and tool discovery **Secure Authentication & Session Management** -- Sessions stored in `.mm/` directory with 0700 permissions +- Sessions stored in `~/.monarch-mcp/` directory (override via `MONARCH_SESSION_DIR`) with 0700 permissions - Proper `RequireMFAException` handling - Structured logging with `structlog` for debugging - Environment variables: `MONARCH_EMAIL`, `MONARCH_PASSWORD`, `MONARCH_MFA_SECRET` @@ -237,7 +237,7 @@ Published to PyPI as `monarch-mcp-jamiew` and to the MCP Registry as `io.github. ### Session Management -- Session files stored in `.mm/` directory (created automatically) +- Session files stored in `~/.monarch-mcp/` directory (created automatically; override via `MONARCH_SESSION_DIR`) - Session invalidation handled gracefully with automatic re-authentication - Use `MONARCH_FORCE_LOGIN=true` to bypass session cache for debugging - Sessions follow Monarch Money API session management patterns @@ -249,7 +249,7 @@ Published to PyPI as `monarch-mcp-jamiew` and to the MCP Registry as `io.github. #### Phase 1 Critical Fixes (All Complete) - **✅ Type Safety**: Eliminated `Any` types, added Pydantic models, strict typing - **✅ FastMCP Migration**: Modern MCP protocol with `@mcp.tool()` decorators -- **✅ Authentication Security**: `.mm/` directory, 0600 permissions, `RequireMFAException` handling +- **✅ Authentication Security**: `~/.monarch-mcp/` directory, 0600 permissions, `RequireMFAException` handling - **✅ Structured Logging**: Context-rich logs with `structlog` - **✅ Complete API Coverage**: All 14 Monarch Money API methods as tools diff --git a/README.md b/README.md index be1d21a..5dcfbde 100644 --- a/README.md +++ b/README.md @@ -224,9 +224,9 @@ By default, transactions return a compact format with the fields that matter: ## Session management -Sessions are cached in a `.mm` directory for faster subsequent logins. If you hit auth issues: +Sessions are cached in `~/.monarch-mcp/` for faster subsequent logins (override the location with the `MONARCH_SESSION_DIR` env var). If you hit auth issues: -- Delete `.mm/session.pickle` to clear the cached session +- Delete `~/.monarch-mcp/session.pickle` to clear the cached session - Set `MONARCH_FORCE_LOGIN=true` in your env config to force a fresh login - Make sure your system clock is accurate (required for TOTP) @@ -281,7 +281,7 @@ uv run scripts/eval_session.py analyze # analyze new entries - The server runs locally on your machine — your credentials live in your MCP client config and **never pass through the LLM**. Only the financial data you actually query is returned to the assistant. - Your credentials have full account access — treat them like passwords - The MFA secret (TOTP key) provides ongoing access -- Session files in `.mm/` contain auth tokens — keep them secure +- Session files in `~/.monarch-mcp/` contain auth tokens — keep them secure - Never commit `.env` or `.mcp.json` files to version control - This is an unofficial API — Monarch Money could change or restrict access at any time diff --git a/server.py b/server.py index 169b705..5a9265f 100644 --- a/server.py +++ b/server.py @@ -921,9 +921,14 @@ class AuthState(Enum): auth_failed_at: float | None = None # Timestamp of last auth failure for cooldown AUTH_RETRY_COOLDOWN_SECONDS = 60 # Wait 60 seconds before retrying after FAILED state -# Secure session directory with proper permissions -session_dir = Path(".mm") -session_dir.mkdir(mode=0o700, exist_ok=True) +# Secure session directory with proper permissions. +# Resolve to an absolute, writable path: many MCP clients (e.g. Claude Desktop) +# launch the server with a read-only working directory like "/", so a relative +# ".mm" would fail with "Read-only file system". Honor MONARCH_SESSION_DIR if set, +# otherwise default to ~/.monarch-mcp which is always writable. +_session_dir_env = os.getenv("MONARCH_SESSION_DIR") +session_dir = Path(_session_dir_env).expanduser() if _session_dir_env else Path.home() / ".monarch-mcp" +session_dir.mkdir(mode=0o700, parents=True, exist_ok=True) session_file = session_dir / "session.pickle"