From 4382b0245054f6a3d5628e6ddbe5eb55b05d81c9 Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 25 Dec 2025 04:35:06 +0000 Subject: [PATCH] fix: use monarchmoney from git main to get budget fix (#119) Since no release has been published in ~11 months, install monarchmoney directly from git main branch which contains the fix for flexible budgets. Also updates gql to >=4.0 as required by the main branch. --- pyproject.toml | 4 +- src/monarch_mcp_custom/server.py | 127 +++++++++++++------------------ 2 files changed, 56 insertions(+), 75 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 49bc68a..f999fe3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,8 +19,8 @@ requires-python = ">=3.12" dependencies = [ "mcp[cli]>=1.0.0", "fastmcp>=0.4.1", - "monarchmoney>=0.1.15", - "gql>=3.4,<4.0", + "monarchmoney @ git+https://github.com/hammem/monarchmoney.git", + "gql>=4.0", "python-dotenv>=1.0.0", "pydantic>=2.0.0", "starlette>=0.35.0", diff --git a/src/monarch_mcp_custom/server.py b/src/monarch_mcp_custom/server.py index 4874caa..9bd0f9d 100644 --- a/src/monarch_mcp_custom/server.py +++ b/src/monarch_mcp_custom/server.py @@ -105,79 +105,60 @@ async def get_transactions( return serialize_json(formatted) -# NOTE: get_budgets is disabled due to upstream Monarch Money API bug -# See budget_bug.md for details -# -# @mcp.tool() -# @retry_on_auth_error() -# async def get_budgets(reason: Optional[str] = None) -> str: -# """Get current budget information.""" -# client = await get_authenticated_client() -# -# try: -# # Disable both legacy and v2 goals to avoid API errors with goals fields -# # The Monarch API appears to have issues with goals-related GraphQL fields -# budgets = await client.get_budgets(use_legacy_goals=False, use_v2_goals=False) -# 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 be a temporary API issue.", -# "raw_error": error_msg, -# } -# ) -# raise -# -# # Build a category lookup from categoryGroups -# category_lookup = {} -# for group in budgets.get("categoryGroups", []): -# for cat in group.get("categories", []): -# category_lookup[cat.get("id")] = { -# "name": cat.get("name"), -# "group": group.get("name"), -# "variability": cat.get("budgetVariability"), -# } -# -# # Process monthly amounts by category -# budget_list = [] -# for item in budgets.get("budgetData", {}).get("monthlyAmountsByCategory", []): -# cat_id = (item.get("category") or {}).get("id") -# cat_info = category_lookup.get(cat_id, {}) -# -# for monthly in item.get("monthlyAmounts", []): -# budget_list.append( -# { -# "month": monthly.get("month"), -# "category": cat_info.get("name"), -# "group": cat_info.get("group"), -# "planned": monthly.get("plannedCashFlowAmount"), -# "actual": monthly.get("actualAmount"), -# "remaining": monthly.get("remainingAmount"), -# "rollover": monthly.get("previousMonthRolloverAmount"), -# } -# ) -# -# # Also include monthly totals summary -# totals = [] -# for total in budgets.get("budgetData", {}).get("totalsByMonth", []): -# totals.append( -# { -# "month": total.get("month"), -# "income_planned": (total.get("totalIncome") or {}).get("plannedAmount"), -# "income_actual": (total.get("totalIncome") or {}).get("actualAmount"), -# "expenses_planned": (total.get("totalExpenses") or {}).get( -# "plannedAmount" -# ), -# "expenses_actual": (total.get("totalExpenses") or {}).get( -# "actualAmount" -# ), -# } -# ) -# -# return serialize_json({"budgets": budget_list, "totals": totals}) +@mcp.tool() +@retry_on_auth_error() +async def get_budgets(reason: Optional[str] = None) -> str: + """Get current budget information.""" + client = await get_authenticated_client() + budgets = await client.get_budgets() + + # Build a category lookup from categoryGroups + category_lookup = {} + for group in budgets.get("categoryGroups", []): + for cat in group.get("categories", []): + category_lookup[cat.get("id")] = { + "name": cat.get("name"), + "group": group.get("name"), + "variability": cat.get("budgetVariability"), + } + + # Process monthly amounts by category + budget_list = [] + for item in budgets.get("budgetData", {}).get("monthlyAmountsByCategory", []): + cat_id = (item.get("category") or {}).get("id") + cat_info = category_lookup.get(cat_id, {}) + + for monthly in item.get("monthlyAmounts", []): + budget_list.append( + { + "month": monthly.get("month"), + "category": cat_info.get("name"), + "group": cat_info.get("group"), + "planned": monthly.get("plannedCashFlowAmount"), + "actual": monthly.get("actualAmount"), + "remaining": monthly.get("remainingAmount"), + "rollover": monthly.get("previousMonthRolloverAmount"), + } + ) + + # Also include monthly totals summary + totals = [] + for total in budgets.get("budgetData", {}).get("totalsByMonth", []): + totals.append( + { + "month": total.get("month"), + "income_planned": (total.get("totalIncome") or {}).get("plannedAmount"), + "income_actual": (total.get("totalIncome") or {}).get("actualAmount"), + "expenses_planned": (total.get("totalExpenses") or {}).get( + "plannedAmount" + ), + "expenses_actual": (total.get("totalExpenses") or {}).get( + "actualAmount" + ), + } + ) + + return serialize_json({"budgets": budget_list, "totals": totals}) def validate_account_id(account_id: str) -> int: