From a229537599fe2eb4a9815f50211eeb1c8086b254 Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 25 Dec 2025 04:20:39 +0000 Subject: [PATCH] fix: add graceful error handling for get_budgets API errors Returns informative error message when Monarch Money API fails, which may occur if budgets are not configured in the account. --- src/monarch_mcp_custom.egg-info/PKG-INFO | 5 +- src/monarch_mcp_custom.egg-info/requires.txt | 3 +- .../__pycache__/auth.cpython-313.pyc | Bin 3575 -> 6140 bytes src/monarch_mcp_custom/server.py | 16 ++++- test_token.py | 56 ++++++++++++++++++ uv.lock | 11 ++++ 6 files changed, 87 insertions(+), 4 deletions(-) create mode 100755 test_token.py diff --git a/src/monarch_mcp_custom.egg-info/PKG-INFO b/src/monarch_mcp_custom.egg-info/PKG-INFO index 8cc0d56..7fbc176 100644 --- a/src/monarch_mcp_custom.egg-info/PKG-INFO +++ b/src/monarch_mcp_custom.egg-info/PKG-INFO @@ -9,13 +9,14 @@ Classifier: Programming Language :: Python :: 3.12 Requires-Python: >=3.12 Description-Content-Type: text/markdown Requires-Dist: mcp[cli]>=1.0.0 +Requires-Dist: fastmcp>=0.4.1 Requires-Dist: monarchmoney>=0.1.15 Requires-Dist: gql<4.0,>=3.4 -Requires-Dist: keyring>=24.0.0 Requires-Dist: python-dotenv>=1.0.0 Requires-Dist: pydantic>=2.0.0 Requires-Dist: starlette>=0.35.0 Requires-Dist: uvicorn>=0.27.0 +Requires-Dist: pyotp>=2.9.0 # Monarch Money Custom MCP Server @@ -49,7 +50,7 @@ docker-compose up -d ## 🔌 Connection The server will be available at: -- **SSE Endpoint**: `http://localhost:8000/mcp/sse` +- **MCP Endpoint**: `http://localhost:8000/mcp` - **Health Check**: `http://localhost:8000/health` ## 🛠️ Tools Included diff --git a/src/monarch_mcp_custom.egg-info/requires.txt b/src/monarch_mcp_custom.egg-info/requires.txt index 12da163..1e4070c 100644 --- a/src/monarch_mcp_custom.egg-info/requires.txt +++ b/src/monarch_mcp_custom.egg-info/requires.txt @@ -1,8 +1,9 @@ mcp[cli]>=1.0.0 +fastmcp>=0.4.1 monarchmoney>=0.1.15 gql<4.0,>=3.4 -keyring>=24.0.0 python-dotenv>=1.0.0 pydantic>=2.0.0 starlette>=0.35.0 uvicorn>=0.27.0 +pyotp>=2.9.0 diff --git a/src/monarch_mcp_custom/__pycache__/auth.cpython-313.pyc b/src/monarch_mcp_custom/__pycache__/auth.cpython-313.pyc index 6240e18f4d4b78e8b0a81bb8029aed043b4b220e..09c4d8eda45ac69b86faba2367e0c6e4ae6ffc1b 100644 GIT binary patch literal 6140 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 L_9p@b+s^+3L&wLq literal 3575 zcmbVO-EZ606~81!iJ~Y={+d6MI=8l4#ETw}F^; z0=)O!bI<+$o%1_~;joC{`)hJcI|?H7ciM12UsX8V0AU@;h#;Af*)fJNPR$Y)YHo~s z%}0E=(#I1XbiOhFYXK6#XqHVy-RWv|H<}F&F_ktEWuAm&Kau3XtZz2VgZct13!Uf< zA8C+-vtc!t0q?UMU5D`W4fpiO}U*1EgkfVk-4K1oG}Uu zu)u9CuN7hJ=kx%ReT*$kEzo6^JjhaY0cZZ@P8bp^WENtD%ChG{K5wdIQ6JriPTuijX$0o2Y!j3X7~IpBH(^^%Nb7LCGyNiwcF4eE4NOVa8nK4QWk8p|^lQS(c5Ocj{c zbJXs=U*a2zmIo8)p|-WTt9BIV0-;B;C9@97gvKpGk3(pLBlKbjjXE~35@Hb21acLu zq?PJLI)4>vouK$OHJF5A1(kg~-*6yA_Jq@qgwyY)cVk!o$nOd_JiNX97va+5gHX#M zVyR)nnsiSpwO!e~wV5cjUf&hs`$G83G7p_kOuE>cLl@De7{}Ze+Yvv-ZJpvl{t7kx z3@i`J0G%FqWS|ERL_^R)zhj{ojtNkzf&xLHmzkHDUNqwc_>}kXmC>{WQt`O~T4%r3 zmiKJAY1Sj4VOWjxT9=RS!`rKG+Lw9?ynfK*63ZySY998#$gFddyhMTR!9CSj+p{al zes?!OYd$!sM;x-?_NWX1-Xlj3@U?j%Cp!6k6Tk~-^49+`bKvV;=_L**EVz7IOgZ_2 z3CT`9hk+|V>OoT+sMIB#$!n1M#Ke@MK}LjA7Gy~UHAx|kwK-sh`oT0`(rC(K>Didr z$2hUcdI2urxTk44q)bX4vZmvU-PMZo6%PcrtC3%FB0f59M_n$w8JFenPo-Y5Vsv7l z!*R+yS*+>UGJ!6zS^%FOa7hPeh?=Fzosu^#>XmdaFa*r!9N)i$t(%}OzyF*2_(UQ| z)Sp-y43FWPc~vo03{)2|X=+9}i4Wg}`fU3omx1BMsi zH$R5rb4qO@3b*}Y+~Kq`%LkkO(R}6u_0P@yC8_@;?HxoK*OuN|Ds`OOiS)0G01N)M zz03xJO$Uw7{JLv(c)zLTcW2)|yD_xWbZ$>NSCYD0tW%13gkI14sC`Jx4d`yU_qzhZs< zf${*Xzq2d7;939M{4t#H&8EbQ%(^h#h2Fai(pE=TB80YHViG~$*2PYcA2vy#f7m4^ z{M^H{9SMfpW_Xad{T%4GgACYghs4Anx82&2h;iF7p2~x?x3AF-q|;d=lTJIYXb0c{ z;n{PxzqqsjHK7r*N*L9A1S-hrC=To(JQ84!CQ3p&tt$l;=5Ywb;}e@Ri=+Y_N)yiO zeTWW0M3sjYt`v-{l~=El^PrZs8bjm58`!pEc3wAyV zc5YnU3HGk|%YM{6uoBsC?k=Hd+26uM?~iQAZ%-T{5X-Ru({MjiMo?{Jk7!kHd4};n Nkp6|BBK@$|e*iVKKIH%a diff --git a/src/monarch_mcp_custom/server.py b/src/monarch_mcp_custom/server.py index 9bd0f9d..9d68d1a 100644 --- a/src/monarch_mcp_custom/server.py +++ b/src/monarch_mcp_custom/server.py @@ -110,7 +110,21 @@ async def get_transactions( async def get_budgets(reason: Optional[str] = None) -> str: """Get current budget information.""" client = await get_authenticated_client() - budgets = await client.get_budgets() + + try: + budgets = await client.get_budgets() + except Exception as e: + error_msg = str(e) + # Check if this is a Monarch API error about budgets not being set up + if "Something went wrong" in error_msg: + return serialize_json( + { + "error": "Budget data unavailable", + "detail": "The Monarch Money API returned an error. This may occur if budgets are not configured in your account.", + "raw_error": error_msg, + } + ) + raise # Build a category lookup from categoryGroups category_lookup = {} diff --git a/test_token.py b/test_token.py new file mode 100755 index 0000000..e7e6737 --- /dev/null +++ b/test_token.py @@ -0,0 +1,56 @@ +import asyncio +import os +from monarchmoney import MonarchMoney +from dotenv import load_dotenv + + +async def test_token(): + load_dotenv(override=True) + + token = os.getenv("MONARCH_TOKEN") + + email = os.getenv("MONARCH_EMAIL") + + password = os.getenv("MONARCH_PASSWORD") + + if token and token.strip(): + # Strip potential prefix + + if token.startswith("MONARCH_TOKEN="): + token = token.replace("MONARCH_TOKEN=", "") + + print(f"Testing with TOKEN: {token[:10]}...") + + mm = MonarchMoney(token=token) + + elif email and password: + print(f"Testing with EMAIL: {email}") + + mm = MonarchMoney() + + try: + await mm.login(email, password) + + print("✅ Login successful with email/password!") + + except Exception as e: + print(f"❌ Login failed: {e}") + + return + + else: + print("❌ No credentials found in .env") + + return + + try: + accounts = await mm.get_accounts() + + print(f"Success! Found {len(accounts.get('accounts', []))} accounts.") + + except Exception as e: + print(f"Error fetching accounts: {e}") + + +if __name__ == "__main__": + asyncio.run(test_token()) diff --git a/uv.lock b/uv.lock index e0246de..6d5c67f 100644 --- a/uv.lock +++ b/uv.lock @@ -913,6 +913,7 @@ dependencies = [ { name = "mcp", extra = ["cli"] }, { name = "monarchmoney" }, { name = "pydantic" }, + { name = "pyotp" }, { name = "python-dotenv" }, { name = "starlette" }, { name = "uvicorn" }, @@ -925,6 +926,7 @@ requires-dist = [ { name = "mcp", extras = ["cli"], specifier = ">=1.0.0" }, { name = "monarchmoney", specifier = ">=0.1.15" }, { name = "pydantic", specifier = ">=2.0.0" }, + { name = "pyotp", specifier = ">=2.9.0" }, { name = "python-dotenv", specifier = ">=1.0.0" }, { name = "starlette", specifier = ">=0.35.0" }, { name = "uvicorn", specifier = ">=0.27.0" }, @@ -1485,6 +1487,15 @@ crypto = [ { name = "cryptography" }, ] +[[package]] +name = "pyotp" +version = "2.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/b2/1d5994ba2acde054a443bd5e2d384175449c7d2b6d1a0614dbca3a63abfc/pyotp-2.9.0.tar.gz", hash = "sha256:346b6642e0dbdde3b4ff5a930b664ca82abfa116356ed48cc42c7d6590d36f63", size = 17763, upload-time = "2023-07-27T23:41:03.295Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/c0/c33c8792c3e50193ef55adb95c1c3c2786fe281123291c2dbf0eaab95a6f/pyotp-2.9.0-py3-none-any.whl", hash = "sha256:81c2e5865b8ac55e825b0358e496e1d9387c811e85bb40e71a3b29b288963612", size = 13376, upload-time = "2023-07-27T23:41:01.685Z" }, +] + [[package]] name = "pyperclip" version = "1.11.0"