feat: add automatic re-authentication with MFA support
All checks were successful
Build and Push Monarch MCP Docker Image / build (push) Successful in 8s

Implement automatic token refresh using stored credentials and TOTP MFA secret. When an API call fails with a 401/unauthorized error, the system now transparently re-authenticates using MONARCH_EMAIL, MONARCH_PASSWORD, and MONARCH_MFA_SECRET, then retries the original request.

Changes:
- Add refresh_authentication() function in auth.py for credential-based login
- Create @retry_on_auth_error decorator to handle and retry failed auth calls
- Apply decorator to all MCP tools (get_accounts, get_transactions, etc.)
- Add MONARCH_MFA_SECRET to .env.example with documentation
- Update login_setup.py to instruct users about required env vars
- Replace PROBLEM.md with PLAN.md documenting the implementation
This commit is contained in:
Ben
2025-12-24 15:45:43 +00:00
parent 27ef7f0e1e
commit 6fc09d956f
6 changed files with 153 additions and 65 deletions

View File

@@ -2,7 +2,6 @@
Monarch Money MCP Server - Custom SSE Implementation.
"""
import os
import logging
import json
from typing import Optional, Any
@@ -13,7 +12,7 @@ from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.routing import Route, Mount
from monarch_mcp_custom.auth import get_authenticated_client
from monarch_mcp_custom.auth import get_authenticated_client, retry_on_auth_error
# Load environment variables
load_dotenv()
@@ -39,6 +38,7 @@ def serialize_json(data: Any) -> str:
@mcp.tool()
@retry_on_auth_error()
async def get_accounts(reason: Optional[str] = None) -> str:
"""Get all financial accounts from Monarch Money."""
try:
@@ -64,6 +64,7 @@ async def get_accounts(reason: Optional[str] = None) -> str:
@mcp.tool()
@retry_on_auth_error()
async def get_transactions(
limit: int = 50,
offset: int = 0,
@@ -112,6 +113,7 @@ async def get_transactions(
@mcp.tool()
@retry_on_auth_error()
async def get_budgets(reason: Optional[str] = None) -> str:
"""Get current budget information."""
try:
@@ -137,6 +139,7 @@ async def get_budgets(reason: Optional[str] = None) -> str:
@mcp.tool()
@retry_on_auth_error()
async def get_account_holdings(account_id: str, reason: Optional[str] = None) -> str:
"""Get investment holdings for a specific account."""
try:
@@ -150,6 +153,7 @@ async def get_account_holdings(account_id: str, reason: Optional[str] = None) ->
@mcp.tool()
@retry_on_auth_error()
async def refresh_accounts(reason: Optional[str] = None) -> str:
"""Request a refresh of account data from financial institutions."""
try: