Support current Monarch auth flow#71
Conversation
df574c4 to
0f5778e
Compare
|
Thanks for the thorough write-up. Holding this open rather than merging because there are blockers that need a coordinated decision. Security concerns.
Architectural conflict.
Breaking change to existing callers.
The email-OTP detection, the api.monarch.com host fix, and the device-uuid plus token storage are all good ideas. The form that lands here will most likely fold those into the cookie-auth path from #59 once that drops draft. |
|
This is conflicting against current main because of the April refactor of server.py into the tools/ package. Could you rebase onto current main so we can evaluate it cleanly? |
Adds a compatibility layer around the monarchmoney dependency for Monarch's current web API, rebased onto the modular tools/ package (PR robcerda#46) instead of the old monolithic server.py. - monarch_auth.py: patch BASE_URL to api.monarch.com, send current web login fields (supports_email_otp/supports_recaptcha/email_otp), detect email-OTP responses separately from TOTP MFA, and build stored-token clients with the saved device-uuid. monarch-client-version is now a documented MONARCH_CLIENT_VERSION constant. - secure_session.py: store token + device-uuid as JSON (still accepting legacy raw-token entries) across the keyring and file-fallback backends. - client.py: use only an existing keyring session; MCP tool calls no longer initiate a noninteractive password login. - app.py: drop dotenv loading; suppress gql.transport INFO logging (with an inline comment) so account payloads are not written to logs. - tools/budgets.py: narrower budget query that avoids stale category-group fields and returns current-month category rows. - login_setup.py: prompt for email verification codes via the current auth flow; README documents email-OTP and keyring-only storage. No dotenv/.env loading and no MONARCH_PASSWORD setup path (addresses the read-only hardening concerns from robcerda#61). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
0f5778e to
bbdd49e
Compare
|
Thanks for the detailed review — that all makes sense. I've force-pushed a version rebased onto the modular Rebase (#5). Ported onto the dotenv (#1). Dropped entirely — no README password (#2). Removed — no Client version (#3). Extracted to a Log suppression (#4). Moved to I also removed the env-credential auto-login from Budget shape (#7). Worth a second look: on current Auth strategy (#6). This is the real open question for you: you mentioned folding these into #59's cookie-auth path once it dropped draft, but #59 is now closed. So there's no longer a converging target. The pieces here — |
Monarch's auth has two distinct failure modes that bit MCP users: 1. trusted_device=False produced short-lived tokens. PR robcerda#71 passed trusted_device=False in the login payload, which Monarch interprets as "untrusted device" and answers with a 1-hour token (tokenExpiration carries an explicit expiry instead of null). This is exactly the pattern documented in hammem/monarchmoney#139: sessions die in 30 to 120 minutes mid-use. Flipping the flag to True and rejecting any tokenExpiration that is not None or "null" gets the long-lived token the web app uses. 2. Cloudflare blocks programmatic login for some accounts. The login endpoint can return 403 with error_code=CAPTCHA_REQUIRED, which the old code treated as a generic MFA challenge. The fix is upstream: monarchmoneycommunity 1.4.0 added set_cookies()/login_with_cookies() so the MCP server can authenticate using a Cookie header captured from a logged-in browser session, bypassing the login endpoint entirely. Cookie sessions also outlive token sessions because the browser's web app refreshes them on every page load. Changes: - pyproject.toml, requirements.txt: bump monarchmoneycommunity>=1.4.0 - monarch_auth.py: - build_login_payload sets trusted_device=True - login_with_current_auth detects CaptchaRequiredException, rejects JWT-shape tokens, rejects any tokenExpiration that is not null - new login_with_browser_cookies() helper delegates to upstream's login_with_cookies and verifies against get_accounts - new cookies_from_client() helper for session persistence - secure_session.py: - save_session_blob stores either token+device_uuid or cookies+ auth_mode (or both) in a JSON keyring entry - load_session returns a typed dict and infers auth_mode from presence of cookies when not explicitly stored - get_authenticated_client calls set_cookies() in cookie mode - backward compatible with bare-token strings and pre-cookie JSON - login_setup.py: refactored to a three-option menu with cookies as the default; deleted the dead "retry on session expired" block - README.md: documents the new cookie path and updates the expired- session troubleshooting entry - Tests: 16 new tests covering cookie roundtrip storage, backward compatibility loading, get_authenticated_client dispatch, and the new monarch_auth helpers. Existing tests updated for trusted_device= True and the gql 4.x DocumentNode location change.
Summary
Monarch's auth flow has changed in ways that break this MCP server's current setup:
https://api.monarch.com, not the oldhttps://api.monarchmoney.comhost.monarchmoneypackage misclassifies the email-code response as a generic 403/MFA-style failure.401 Unauthorizedon GraphQL calls.get_budgetscan fail even after auth is fixed.This PR adds a small compatibility layer around the existing
monarchmoneydependency instead of rewriting the MCP tools:MonarchMoneyEndpoints.BASE_URLto the current Monarch API hostsupports_email_otp,supports_recaptcha, andemail_otplogin_setup.pyto prompt for email verification codes and save the resulting session to keyringlogin_setup.py; MCP tool calls now use only an existing keyring session and do not trigger noninteractive password-login attemptsUpstream context
Related upstream dependency issues:
hammem/monarchmoney#184: API domain changed toapi.monarch.comhammem/monarchmoney#196: first non-interactive auth attempt returns 429; commenters report the new host fixes that parthammem/monarchmoney#137: app update warning / auth 403 is misclassified as MFAThe TypeScript
@hakimelek/monarchmoneypackage documents the same current behavior: Monarch may require email OTP for new devices/sessions even when MFA is off, and the OTP is submitted asemail_otp.Verification
Additional local smoke verification completed:
login_setup.pyloaded credentials from.env.local