Skip to main content
Glama
dgalarza

YNAB MCP Server

by dgalarza

get_category_spending_summary

Analyze category spending patterns over time to track expenses, view monthly breakdowns, and understand budget performance with visual charts.

Instructions

Get spending summary for a category over a date range.

Args:
    budget_id: The ID of the budget (use 'last-used' for default budget)
    category_id: The category ID to analyze
    since_date: Start date (YYYY-MM-DD format)
    until_date: End date (YYYY-MM-DD format)
    include_graph: Include terminal graph visualization (default: True)

Returns:
    JSON string with summary including total spent, average per month, transaction count, monthly breakdown, and optional graph

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
budget_idYes
category_idYes
include_graphNo
since_dateYes
until_dateYes

Implementation Reference

  • MCP tool handler and registration via @mcp.tool() decorator. Defines input schema via type hints and docstring. Executes tool logic by delegating to YNABClient.get_category_spending_summary and returns JSON.
    @mcp.tool()
    async def get_category_spending_summary(
        budget_id: str,
        category_id: str,
        since_date: str,
        until_date: str,
        include_graph: bool = True,
    ) -> str:
        """Get spending summary for a category over a date range.
    
        Args:
            budget_id: The ID of the budget (use 'last-used' for default budget)
            category_id: The category ID to analyze
            since_date: Start date (YYYY-MM-DD format)
            until_date: End date (YYYY-MM-DD format)
            include_graph: Include terminal graph visualization (default: True)
    
        Returns:
            JSON string with summary including total spent, average per month, transaction count, monthly breakdown, and optional graph
        """
        client = get_ynab_client()
        result = await client.get_category_spending_summary(
            budget_id, category_id, since_date, until_date, include_graph
        )
        return json.dumps(result, indent=2)
  • Core helper method in YNABClient that implements the spending summary logic: fetches transactions via YNAB API, filters by category and date range, aggregates total spent, transaction count, monthly breakdown, average per month, and generates optional terminal graph.
    async def get_category_spending_summary(
        self,
        budget_id: str,
        category_id: str,
        since_date: str,
        until_date: str,
        include_graph: bool = True,
    ) -> dict[str, Any]:
        """Get spending summary for a category over a date range.
    
        Args:
            budget_id: The budget ID or 'last-used'
            category_id: The category ID to analyze
            since_date: Start date (YYYY-MM-DD)
            until_date: End date (YYYY-MM-DD)
            include_graph: Include terminal graph visualization (default: True)
    
        Returns:
            Summary with total spent, average, transaction count, and monthly breakdown
        """
        try:
            # Get transactions for the category
            url = f"{self.api_base_url}/budgets/{budget_id}/transactions"
            params = {"since_date": since_date}
    
            result = await self._make_request_with_retry("get", url, params=params)
    
            txn_data = result["data"]["transactions"]
    
            # Filter and aggregate
            total_spent = 0
            transaction_count = 0
            monthly_totals = {}
    
            for txn in txn_data:
                # Filter by category and date range
                if txn.get("category_id") != category_id:
                    continue
                if txn["date"] > until_date:
                    continue
    
                amount = txn["amount"] / 1000 if txn.get("amount") else 0
                total_spent += amount
                transaction_count += 1
    
                # Track monthly totals
                month_key = txn["date"][:7]  # YYYY-MM
                if month_key not in monthly_totals:
                    monthly_totals[month_key] = 0
                monthly_totals[month_key] += amount
    
            # Calculate average per month
            num_months = len(monthly_totals) if monthly_totals else 1
            average_per_month = total_spent / num_months if num_months > 0 else 0
    
            # Convert monthly totals to sorted list
            monthly_breakdown = [
                {"month": month, "spent": amount}
                for month, amount in sorted(monthly_totals.items())
            ]
    
            result = {
                "category_id": category_id,
                "date_range": {"start": since_date, "end": until_date},
                "total_spent": total_spent,
                "transaction_count": transaction_count,
                "average_per_month": average_per_month,
                "num_months": num_months,
                "monthly_breakdown": monthly_breakdown,
            }
    
            # Add graph if requested
            if include_graph and monthly_breakdown:
                graph_data = [(item["month"], item["spent"]) for item in monthly_breakdown]
                result["graph"] = self._generate_graph(
                    graph_data, f"Monthly Spending: {since_date} to {until_date}"
                )
    
            return result
        except Exception as e:
  • Supporting utility method used by get_category_spending_summary to generate terminal-based graph visualization of monthly spending data using termgraph library.
    def _generate_graph(self, data: list[tuple], title: str = "") -> str:
        """Generate a terminal graph using termgraph.
    
        Args:
            data: List of (label, value) tuples
            title: Graph title
    
        Returns:
            String containing the terminal graph
        """
        if not data:
            return ""
    
        # Capture termgraph output
        old_stdout = sys.stdout
        sys.stdout = StringIO()
    
        try:
            # Prepare data for termgraph
            labels = [label for label, _ in data]
            values = [[abs(value)] for _, value in data]
    
            # Configure termgraph
            args = {
                "stacked": False,
                "width": 50,
                "format": "{:.2f}",
                "suffix": "",
                "no_labels": False,
                "color": None,
                "vertical": False,
                "different_scale": False,
                "calendar": False,
                "start_dt": None,
                "custom_tick": "",
                "delim": "",
                "verbose": False,
                "label_before": False,
                "histogram": False,
                "no_values": False,
            }
    
            # Print title
            if title:
                print(f"\n{title}")
                print("=" * len(title))
    
            # Generate graph
            tg.chart(colors=[], data=values, args=args, labels=labels)
    
            # Get the output
            output = sys.stdout.getvalue()
            return output
    
        finally:
            sys.stdout = old_stdout

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/dgalarza/ynab-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server