fix: add graceful error handling for get_budgets API errors
All checks were successful
Build and Push Monarch MCP Docker Image / build (push) Successful in 16s

Returns informative error message when Monarch Money API fails,
which may occur if budgets are not configured in the account.
This commit is contained in:
Ben
2025-12-25 04:20:39 +00:00
parent 4a309cbfb3
commit a229537599
6 changed files with 87 additions and 4 deletions

View File

@@ -9,13 +9,14 @@ Classifier: Programming Language :: Python :: 3.12
Requires-Python: >=3.12 Requires-Python: >=3.12
Description-Content-Type: text/markdown Description-Content-Type: text/markdown
Requires-Dist: mcp[cli]>=1.0.0 Requires-Dist: mcp[cli]>=1.0.0
Requires-Dist: fastmcp>=0.4.1
Requires-Dist: monarchmoney>=0.1.15 Requires-Dist: monarchmoney>=0.1.15
Requires-Dist: gql<4.0,>=3.4 Requires-Dist: gql<4.0,>=3.4
Requires-Dist: keyring>=24.0.0
Requires-Dist: python-dotenv>=1.0.0 Requires-Dist: python-dotenv>=1.0.0
Requires-Dist: pydantic>=2.0.0 Requires-Dist: pydantic>=2.0.0
Requires-Dist: starlette>=0.35.0 Requires-Dist: starlette>=0.35.0
Requires-Dist: uvicorn>=0.27.0 Requires-Dist: uvicorn>=0.27.0
Requires-Dist: pyotp>=2.9.0
# Monarch Money Custom MCP Server # Monarch Money Custom MCP Server
@@ -49,7 +50,7 @@ docker-compose up -d
## 🔌 Connection ## 🔌 Connection
The server will be available at: 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` - **Health Check**: `http://localhost:8000/health`
## 🛠️ Tools Included ## 🛠️ Tools Included

View File

@@ -1,8 +1,9 @@
mcp[cli]>=1.0.0 mcp[cli]>=1.0.0
fastmcp>=0.4.1
monarchmoney>=0.1.15 monarchmoney>=0.1.15
gql<4.0,>=3.4 gql<4.0,>=3.4
keyring>=24.0.0
python-dotenv>=1.0.0 python-dotenv>=1.0.0
pydantic>=2.0.0 pydantic>=2.0.0
starlette>=0.35.0 starlette>=0.35.0
uvicorn>=0.27.0 uvicorn>=0.27.0
pyotp>=2.9.0

View File

@@ -110,7 +110,21 @@ async def get_transactions(
async def get_budgets(reason: Optional[str] = None) -> str: async def get_budgets(reason: Optional[str] = None) -> str:
"""Get current budget information.""" """Get current budget information."""
client = await get_authenticated_client() 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 # Build a category lookup from categoryGroups
category_lookup = {} category_lookup = {}

56
test_token.py Executable file
View File

@@ -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())

11
uv.lock generated
View File

@@ -913,6 +913,7 @@ dependencies = [
{ name = "mcp", extra = ["cli"] }, { name = "mcp", extra = ["cli"] },
{ name = "monarchmoney" }, { name = "monarchmoney" },
{ name = "pydantic" }, { name = "pydantic" },
{ name = "pyotp" },
{ name = "python-dotenv" }, { name = "python-dotenv" },
{ name = "starlette" }, { name = "starlette" },
{ name = "uvicorn" }, { name = "uvicorn" },
@@ -925,6 +926,7 @@ requires-dist = [
{ name = "mcp", extras = ["cli"], specifier = ">=1.0.0" }, { name = "mcp", extras = ["cli"], specifier = ">=1.0.0" },
{ name = "monarchmoney", specifier = ">=0.1.15" }, { name = "monarchmoney", specifier = ">=0.1.15" },
{ name = "pydantic", specifier = ">=2.0.0" }, { name = "pydantic", specifier = ">=2.0.0" },
{ name = "pyotp", specifier = ">=2.9.0" },
{ name = "python-dotenv", specifier = ">=1.0.0" }, { name = "python-dotenv", specifier = ">=1.0.0" },
{ name = "starlette", specifier = ">=0.35.0" }, { name = "starlette", specifier = ">=0.35.0" },
{ name = "uvicorn", specifier = ">=0.27.0" }, { name = "uvicorn", specifier = ">=0.27.0" },
@@ -1485,6 +1487,15 @@ crypto = [
{ name = "cryptography" }, { 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]] [[package]]
name = "pyperclip" name = "pyperclip"
version = "1.11.0" version = "1.11.0"