Skip to main content
Glama
boldcommerce

Magento 2 MCP Server

by boldcommerce

get_revenue

Calculate total revenue for a Magento 2 store within specified date ranges and order status filters, with tax inclusion options.

Instructions

Get the total revenue for a given date range

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
date_rangeYesDate range expression (e.g., 'today', 'yesterday', 'last week', 'this month', 'YTD', or a specific date range like '2023-01-01 to 2023-01-31')
statusNoFilter by order status (e.g., 'processing', 'complete', 'pending')
include_taxNoWhether to include tax in the revenue calculation (default: true)

Implementation Reference

  • mcp-server.js:851-938 (registration)
    Registration of the 'get_revenue' MCP tool using server.tool(), including description, Zod input schema, and the complete inline async handler function that implements the revenue calculation logic.
    server.tool(
      "get_revenue",
      "Get the total revenue for a given date range",
      {
        date_range: z.string().describe("Date range expression (e.g., 'today', 'yesterday', 'last week', 'this month', 'YTD', or a specific date range like '2023-01-01 to 2023-01-31')"),
        status: z.string().optional().describe("Filter by order status (e.g., 'processing', 'complete', 'pending')"),
        include_tax: z.boolean().optional().describe("Whether to include tax in the revenue calculation (default: true)")
      },
      async ({ date_range, status, include_tax = true }) => {
        try {
          // Parse the date range expression
          const dateRange = parseDateExpression(date_range);
          
          // Build the search criteria for the date range
          let searchCriteria = buildDateRangeFilter('created_at', dateRange.startDate, dateRange.endDate);
          
          // Add status filter if provided
          if (status) {
            searchCriteria += `&searchCriteria[filter_groups][2][filters][0][field]=status&` +
                              `searchCriteria[filter_groups][2][filters][0][value]=${encodeURIComponent(status)}&` +
                              `searchCriteria[filter_groups][2][filters][0][condition_type]=eq`;
          }
          
          // Fetch all orders using the helper function
          const allOrders = await fetchAllPages('/orders', searchCriteria);
          
          // Calculate total revenue
          let totalRevenue = 0;
          let totalTax = 0;
          let orderCount = 0;
          
          if (allOrders && Array.isArray(allOrders)) {
            orderCount = allOrders.length;
            
            allOrders.forEach(order => {
              // Use grand_total which includes tax, shipping, etc.
              totalRevenue += parseFloat(order.grand_total || 0);
              
              // Track tax separately
              totalTax += parseFloat(order.tax_amount || 0);
            });
          }
          
          // Adjust revenue if tax should be excluded
          const revenueWithoutTax = totalRevenue - totalTax;
          const finalRevenue = include_tax ? totalRevenue : revenueWithoutTax;
          
          // Format the response
          const result = {
            query: {
              date_range: dateRange.description,
              status: status || 'All',
              include_tax: include_tax,
              period: {
                start_date: format(dateRange.startDate, 'yyyy-MM-dd'),
                end_date: format(dateRange.endDate, 'yyyy-MM-dd')
              }
            },
            result: {
              revenue: parseFloat(finalRevenue.toFixed(2)),
              currency: 'USD', // This should be dynamically determined from the store configuration
              order_count: orderCount,
              average_order_value: orderCount > 0 ? parseFloat((finalRevenue / orderCount).toFixed(2)) : 0,
              tax_amount: parseFloat(totalTax.toFixed(2))
            }
          };
          
          return {
            content: [
              {
                type: "text",
                text: JSON.stringify(result, null, 2)
              }
            ]
          };
        } catch (error) {
          return {
            content: [
              {
                type: "text",
                text: `Error fetching revenue: ${error.message}`
              }
            ],
            isError: true
          };
        }
      }
    );
  • The core handler function for the get_revenue tool. Parses date range, applies filters, fetches all orders via Magento API, computes total revenue (with optional tax exclusion), order count, average order value, and returns structured JSON response.
    async ({ date_range, status, include_tax = true }) => {
      try {
        // Parse the date range expression
        const dateRange = parseDateExpression(date_range);
        
        // Build the search criteria for the date range
        let searchCriteria = buildDateRangeFilter('created_at', dateRange.startDate, dateRange.endDate);
        
        // Add status filter if provided
        if (status) {
          searchCriteria += `&searchCriteria[filter_groups][2][filters][0][field]=status&` +
                            `searchCriteria[filter_groups][2][filters][0][value]=${encodeURIComponent(status)}&` +
                            `searchCriteria[filter_groups][2][filters][0][condition_type]=eq`;
        }
        
        // Fetch all orders using the helper function
        const allOrders = await fetchAllPages('/orders', searchCriteria);
        
        // Calculate total revenue
        let totalRevenue = 0;
        let totalTax = 0;
        let orderCount = 0;
        
        if (allOrders && Array.isArray(allOrders)) {
          orderCount = allOrders.length;
          
          allOrders.forEach(order => {
            // Use grand_total which includes tax, shipping, etc.
            totalRevenue += parseFloat(order.grand_total || 0);
            
            // Track tax separately
            totalTax += parseFloat(order.tax_amount || 0);
          });
        }
        
        // Adjust revenue if tax should be excluded
        const revenueWithoutTax = totalRevenue - totalTax;
        const finalRevenue = include_tax ? totalRevenue : revenueWithoutTax;
        
        // Format the response
        const result = {
          query: {
            date_range: dateRange.description,
            status: status || 'All',
            include_tax: include_tax,
            period: {
              start_date: format(dateRange.startDate, 'yyyy-MM-dd'),
              end_date: format(dateRange.endDate, 'yyyy-MM-dd')
            }
          },
          result: {
            revenue: parseFloat(finalRevenue.toFixed(2)),
            currency: 'USD', // This should be dynamically determined from the store configuration
            order_count: orderCount,
            average_order_value: orderCount > 0 ? parseFloat((finalRevenue / orderCount).toFixed(2)) : 0,
            tax_amount: parseFloat(totalTax.toFixed(2))
          }
        };
        
        return {
          content: [
            {
              type: "text",
              text: JSON.stringify(result, null, 2)
            }
          ]
        };
      } catch (error) {
        return {
          content: [
            {
              type: "text",
              text: `Error fetching revenue: ${error.message}`
            }
          ],
          isError: true
        };
      }
    }
  • Zod schema defining input parameters for the get_revenue tool: required date_range string, optional status and include_tax.
    {
      date_range: z.string().describe("Date range expression (e.g., 'today', 'yesterday', 'last week', 'this month', 'YTD', or a specific date range like '2023-01-01 to 2023-01-31')"),
      status: z.string().optional().describe("Filter by order status (e.g., 'processing', 'complete', 'pending')"),
      include_tax: z.boolean().optional().describe("Whether to include tax in the revenue calculation (default: true)")
    },
  • Helper function to parse natural language date range expressions into startDate, endDate (Date objects) and a human-readable description, used by get_revenue and similar tools.
    function parseDateExpression(dateExpression) {
      const now = new Date();
      const currentYear = now.getFullYear();
      const currentMonth = now.getMonth();
      const currentDay = now.getDate();
      
      // Normalize the date expression
      const normalizedExpression = dateExpression.toLowerCase().trim();
      
      // Handle relative date expressions
      switch (normalizedExpression) {
        case 'today':
          return {
            startDate: startOfDay(now),
            endDate: endOfDay(now),
            description: 'Today'
          };
        case 'yesterday':
          const yesterday = subDays(now, 1);
          return {
            startDate: startOfDay(yesterday),
            endDate: endOfDay(yesterday),
            description: 'Yesterday'
          };
        case 'this week':
          return {
            startDate: startOfWeek(now, { weekStartsOn: 1 }), // Week starts on Monday
            endDate: endOfDay(now),
            description: 'This week'
          };
        case 'last week':
          const lastWeekStart = subDays(startOfWeek(now, { weekStartsOn: 1 }), 7);
          const lastWeekEnd = subDays(endOfWeek(now, { weekStartsOn: 1 }), 7);
          return {
            startDate: lastWeekStart,
            endDate: lastWeekEnd,
            description: 'Last week'
          };
        case 'this month':
          return {
            startDate: startOfMonth(now),
            endDate: endOfDay(now),
            description: 'This month'
          };
        case 'last month':
          const lastMonth = new Date(currentYear, currentMonth - 1, 1);
          return {
            startDate: startOfMonth(lastMonth),
            endDate: endOfMonth(lastMonth),
            description: 'Last month'
          };
        case 'ytd':
        case 'this ytd':
        case 'this year to date':
        case 'year to date':
          return {
            startDate: startOfYear(now),
            endDate: endOfDay(now),
            description: 'Year to date'
          };
        case 'last year':
          const lastYear = new Date(currentYear - 1, 0, 1);
          return {
            startDate: startOfYear(lastYear),
            endDate: endOfYear(lastYear),
            description: 'Last year'
          };
        default:
          // Try to parse as ISO date or other common formats
          try {
            // Check if it's a single date (not a range)
            const parsedDate = parseISO(normalizedExpression);
            if (isValid(parsedDate)) {
              return {
                startDate: startOfDay(parsedDate),
                endDate: endOfDay(parsedDate),
                description: format(parsedDate, 'yyyy-MM-dd')
              };
            }
            
            // Check if it's a date range in format "YYYY-MM-DD to YYYY-MM-DD"
            const rangeParts = normalizedExpression.split(' to ');
            if (rangeParts.length === 2) {
              const startDate = parseISO(rangeParts[0]);
              const endDate = parseISO(rangeParts[1]);
              
              if (isValid(startDate) && isValid(endDate)) {
                return {
                  startDate: startOfDay(startDate),
                  endDate: endOfDay(endDate),
                  description: `${format(startDate, 'yyyy-MM-dd')} to ${format(endDate, 'yyyy-MM-dd')}`
                };
              }
            }
            
            // If we can't parse it, throw an error
            throw new Error(`Unable to parse date expression: ${dateExpression}`);
          } catch (error) {
            throw new Error(`Invalid date expression: ${dateExpression}. ${error.message}`);
          }
      }
    }
  • Helper to fetch and concatenate all paginated results from a Magento API endpoint, handling dynamic pagination based on total_count.
    async function fetchAllPages(endpoint, baseSearchCriteria) {
      const pageSize = 100; // Or make this configurable if needed
      let currentPage = 1;
      let allItems = [];
      let totalCount = 0;
      
      do {
        // Build search criteria for the current page, ensuring baseSearchCriteria doesn't already have pagination
        let currentPageSearchCriteria = baseSearchCriteria;
        if (!currentPageSearchCriteria.includes('searchCriteria[pageSize]')) {
          currentPageSearchCriteria += `&searchCriteria[pageSize]=${pageSize}`;
        }
        if (!currentPageSearchCriteria.includes('searchCriteria[currentPage]')) {
          currentPageSearchCriteria += `&searchCriteria[currentPage]=${currentPage}`;
        } else {
          // If currentPage is already there, replace it (less common case)
          currentPageSearchCriteria = currentPageSearchCriteria.replace(/searchCriteria\[currentPage\]=\d+/, `searchCriteria[currentPage]=${currentPage}`);
        }
    
        // Make the API call for the current page
        const responseData = await callMagentoApi(`${endpoint}?${currentPageSearchCriteria}`);
        
        if (responseData.items && Array.isArray(responseData.items)) {
          allItems = allItems.concat(responseData.items);
        }
        
        // Update total count (only needs to be set once)
        if (currentPage === 1) {
          totalCount = responseData.total_count || 0;
        }
        
        // Check if we need to fetch more pages
        if (totalCount <= allItems.length || !responseData.items || responseData.items.length < pageSize) {
          break; // Exit loop if all items are fetched or last page had less than pageSize items
        }
        
        currentPage++;
        
      } while (true); // Loop continues until break
      
      return allItems; // Return the aggregated list of items
    }
  • Helper to construct Magento API searchCriteria query string for filtering records by date range on a given field.
    function buildDateRangeFilter(field, startDate, endDate) {
      const formattedStartDate = formatDateForMagento(startDate);
      const formattedEndDate = formatDateForMagento(endDate);
      
      return [
        `searchCriteria[filter_groups][0][filters][0][field]=${field}`,
        `searchCriteria[filter_groups][0][filters][0][value]=${encodeURIComponent(formattedStartDate)}`,
        `searchCriteria[filter_groups][0][filters][0][condition_type]=gteq`,
        `searchCriteria[filter_groups][1][filters][0][field]=${field}`,
        `searchCriteria[filter_groups][1][filters][0][value]=${encodeURIComponent(formattedEndDate)}`,
        `searchCriteria[filter_groups][1][filters][0][condition_type]=lteq`
      ].join('&');
    }
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden of behavioral disclosure. While 'Get' implies a read-only operation, the description doesn't mention whether this requires specific permissions, how results are formatted, if there are rate limits, or what happens with invalid date ranges. For a revenue calculation tool with zero annotation coverage, this is insufficient.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, efficient sentence that states the core purpose without unnecessary words. It's appropriately sized and front-loaded with the essential information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a revenue calculation tool with 3 parameters, 100% schema coverage, and no output schema, the description is minimally adequate. It states what the tool does but lacks behavioral context (permissions, rate limits, result format) and usage guidance relative to siblings. The absence of annotations and output schema means the description should do more to compensate.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all three parameters thoroughly. The description mentions 'date range' which aligns with the required parameter but adds no additional semantic context beyond what the schema provides. The baseline score of 3 is appropriate when the schema does the heavy lifting.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose with a specific verb ('Get') and resource ('total revenue'), and specifies the scope ('for a given date range'). However, it doesn't differentiate from sibling tools like 'get_revenue_by_country', which suggests this tool provides overall revenue rather than country-specific breakdowns.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives like 'get_revenue_by_country' or 'get_product_sales'. It mentions a date range but doesn't specify when this tool is preferred over other revenue-related tools or when it should not be used.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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/boldcommerce/magento2-mcp'

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