Skip to main content
Glama
brandon-butterwick

MRP Calculator MCP Server

calculate_order_need

Determine MRP order requirements by analyzing inventory, forecasts, and delivery schedules to optimize stock levels and meet demand accurately.

Instructions

Calculate MRP order need based on forecast, inventory, and delivery schedule

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
analysis_dateYes
batch_sizesNo
current_balanceYes
delivery_scheduleYes
forecast_periodsYes
must_order_pointYes
open_ordersYes
sku_locationYes

Implementation Reference

  • Core handler function that executes the MRP order need calculation logic, including balance projections, demand summation, batch optimization, and validation messages.
    export function calculateMRP(input: MRPInput): MRPOutput {
        const steps: CalculationStep[] = [];
        const projectedBalances = [];
    
        // Step 1: Calculate Starting Balance
        const startingBalance = input.current_balance + input.open_orders.reduce((sum, order) => sum + order.quantity, 0);
        steps.push({
            step: 'starting_balance',
            description: 'Current balance + Open orders',
            value: startingBalance
        });
    
        // Step 2: Calculate Total Demand
        const totalDemand = input.forecast_periods.reduce((sum, period) => sum + period.quantity, 0);
        steps.push({
            step: 'total_demand',
            description: 'Sum of all forecast periods',
            value: totalDemand
        });
    
        // Step 3: Calculate Order Need
        const orderNeed = (input.must_order_point + totalDemand) - startingBalance;
        steps.push({
            step: 'order_need',
            description: '(Must-order point + Total demand) - Starting balance',
            value: orderNeed
        });
    
        // Step 4: Calculate Daily Projected Balances
        let currentBalance = startingBalance;
        let currentDate = new Date(input.analysis_date);
        const secondDelivery = new Date(input.delivery_schedule.second_delivery);
    
        while (currentDate < secondDelivery) {
            const dateStr = currentDate.toISOString().split('T')[0];
            const adjustments = [];
            let endingBalance = currentBalance;
    
            // Check for open orders on this date
            const todaysOrders = input.open_orders.filter(order =>
                order.delivery_date === dateStr
            );
    
            if (todaysOrders.length > 0) {
                const orderSum = todaysOrders.reduce((sum, order) => sum + order.quantity, 0);
                adjustments.push({
                    type: 'RECEIPT',
                    quantity: orderSum,
                    reason: 'Open order delivery'
                });
                endingBalance += orderSum;
            }
    
            // Apply forecast demand
            const forecastPeriod = input.forecast_periods.find(period =>
                dateStr >= period.start_date && dateStr <= period.end_date
            );
    
            if (forecastPeriod) {
                const daysInPeriod = (new Date(forecastPeriod.end_date).getTime() -
                    new Date(forecastPeriod.start_date).getTime()) /
                    (1000 * 60 * 60 * 24) + 1;
                const dailyDemand = forecastPeriod.quantity / daysInPeriod;
    
                adjustments.push({
                    type: 'DEMAND',
                    quantity: -dailyDemand,
                    reason: 'Daily forecast demand'
                });
                endingBalance -= dailyDemand;
            }
    
            projectedBalances.push({
                date: dateStr,
                starting_balance: currentBalance,
                adjustments,
                ending_balance: endingBalance
            });
    
            currentBalance = endingBalance;
            currentDate.setDate(currentDate.getDate() + 1);
        }
    
        // Step 5: Apply Batch Size Optimization
        let finalOrderNeed = orderNeed;
        if (input.batch_sizes && input.batch_sizes.length > 0 && orderNeed > 0) {
            const batchSize = input.batch_sizes.find(size => size >= orderNeed) ||
                input.batch_sizes[input.batch_sizes.length - 1];
            finalOrderNeed = batchSize;
    
            steps.push({
                step: 'batch_optimization',
                description: 'Rounded to nearest valid batch size',
                value: finalOrderNeed
            });
        }
    
        // Prepare validation messages
        const messages = [];
        const lowestProjectedBalance = Math.min(...projectedBalances.map(pb => pb.ending_balance));
    
        if (lowestProjectedBalance < input.must_order_point) {
            messages.push(`Warning: Projected balance falls below must-order point (${input.must_order_point})`);
        }
    
        return {
            order_need: finalOrderNeed,
            calculation_steps: steps,
            validation: {
                status: messages.length > 0 ? 'WARNING' : 'SUCCESS',
                messages
            },
            projected_balances: projectedBalances
        };
  • MCP server request handler for tool calls, specifically dispatches 'calculate_order_need', handles input parsing, validation, execution via calculateMRP, and response formatting.
    this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
        if (request.params.name !== 'calculate_order_need') {
            return {
                content: [{
                    type: 'text',
                    text: `Unknown tool: ${request.params.name}`,
                }],
                isError: true,
            };
        }
    
        try {
            // Type assertion with validation
            const rawInput = request.params.arguments as Record<string, unknown>;
            const deliverySchedule = rawInput.delivery_schedule as Record<string, unknown>;
    
            const input: MRPInput = {
                sku_location: rawInput.sku_location as { sku: string; location: string },
                analysis_date: String(rawInput.analysis_date),
                must_order_point: Number(rawInput.must_order_point),
                current_balance: Number(rawInput.current_balance),
                open_orders: (rawInput.open_orders as Array<any>).map(order => ({
                    quantity: Number(order.quantity),
                    delivery_date: String(order.delivery_date)
                })),
                delivery_schedule: {
                    first_delivery: String(deliverySchedule.first_delivery),
                    second_delivery: String(deliverySchedule.second_delivery),
                    lead_time: Number(deliverySchedule.lead_time)
                },
                forecast_periods: (rawInput.forecast_periods as Array<any>).map(period => ({
                    start_date: String(period.start_date),
                    end_date: String(period.end_date),
                    quantity: Number(period.quantity)
                })),
                batch_sizes: rawInput.batch_sizes ? (rawInput.batch_sizes as number[]) : undefined
            };
    
            // Validate input
            const validationResult = validateMRPInput(input);
            if (!validationResult.isValid) {
                return {
                    content: [{
                        type: 'text',
                        text: `Validation failed:\n${validationResult.messages.map(m => `${m.type}: ${m.message}`).join('\n')}`,
                    }],
                    isError: true,
                };
            }
    
            // Calculate MRP
            const result = calculateMRP(input);
    
            // Format response
            const response = {
                order_need: result.order_need,
                calculation_steps: result.calculation_steps.map(step =>
                    `${step.step}: ${step.description} = ${step.value}`
                ).join('\n'),
                projected_balances: result.projected_balances.map(pb =>
                    `${pb.date}: ${pb.starting_balance} → ${pb.ending_balance}`
                ).join('\n'),
                validation: result.validation.messages.join('\n')
            };
    
            return {
                content: [{
                    type: 'text',
                    text: JSON.stringify(response, null, 2),
                }],
            };
        } catch (error: unknown) {
            const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
            return {
                content: [{
                    type: 'text',
                    text: `Error calculating MRP: ${errorMessage}`,
                }],
                isError: true,
            };
        }
    });
  • src/index.ts:21-74 (registration)
    Registration of the 'calculate_order_need' tool in MCP server capabilities, defining name, description, and input schema.
    calculate_order_need: {
        name: 'calculate_order_need',
        description: 'Calculate MRP order need based on forecast, inventory, and delivery schedule',
        inputSchema: {
            type: 'object',
            properties: {
                sku_location: {
                    type: 'object',
                    properties: {
                        sku: { type: 'string' },
                        location: { type: 'string' }
                    },
                    required: ['sku', 'location']
                },
                analysis_date: { type: 'string' },
                must_order_point: { type: 'number' },
                current_balance: { type: 'number' },
                open_orders: {
                    type: 'array',
                    items: {
                        type: 'object',
                        properties: {
                            quantity: { type: 'number' },
                            delivery_date: { type: 'string' }
                        }
                    }
                },
                delivery_schedule: {
                    type: 'object',
                    properties: {
                        first_delivery: { type: 'string' },
                        second_delivery: { type: 'string' },
                        lead_time: { type: 'number' }
                    }
                },
                forecast_periods: {
                    type: 'array',
                    items: {
                        type: 'object',
                        properties: {
                            start_date: { type: 'string' },
                            end_date: { type: 'string' },
                            quantity: { type: 'number' }
                        }
                    }
                },
                batch_sizes: {
                    type: 'array',
                    items: { type: 'number' }
                }
            },
            required: ['sku_location', 'analysis_date', 'must_order_point', 'current_balance', 'open_orders', 'delivery_schedule', 'forecast_periods']
        }
    }
  • TypeScript interface defining the input structure (MRPInput) for the calculate_order_need tool, matching the JSON schema.
    export interface MRPInput {
        sku_location: {
            sku: string;
            location: string;
        };
        analysis_date: string;  // ISO 8601
        must_order_point: number;
        current_balance: number;
        open_orders: Array<{
            quantity: number;
            delivery_date: string;  // ISO 8601
        }>;
        delivery_schedule: {
            first_delivery: string;   // ISO 8601
            second_delivery: string;  // ISO 8601
            lead_time: number;
        };
        forecast_periods: Array<{
            start_date: string;    // ISO 8601
            end_date: string;      // ISO 8601
            quantity: number;
        }>;
        batch_sizes?: number[];  // Optional
    }
  • Helper function to validate the input parameters for the tool, checking dates, numbers, required fields, and producing error/warning messages.
    export function validateMRPInput(input: MRPInput): ValidationResult {
        const messages: ValidationMessage[] = [];
    
        // Required fields validation
        if (!input.sku_location?.sku || !input.sku_location?.location) {
            messages.push({
                type: 'ERROR',
                message: 'SKU and location are required'
            });
        }
    
        // Numeric validations
        if (input.must_order_point < 0) {
            messages.push({
                type: 'ERROR',
                message: 'Must-order point cannot be negative'
            });
        }
    
        if (input.current_balance < 0) {
            messages.push({
                type: 'WARNING',
                message: 'Current balance is negative'
            });
        }
    
        // Date validations
        try {
            const analysisDate = new Date(input.analysis_date);
            const firstDelivery = new Date(input.delivery_schedule.first_delivery);
            const secondDelivery = new Date(input.delivery_schedule.second_delivery);
    
            if (firstDelivery <= analysisDate) {
                messages.push({
                    type: 'ERROR',
                    message: 'First delivery date must be after analysis date'
                });
            }
    
            if (secondDelivery <= firstDelivery) {
                messages.push({
                    type: 'ERROR',
                    message: 'Second delivery date must be after first delivery date'
                });
            }
        } catch (e) {
            messages.push({
                type: 'ERROR',
                message: 'Invalid date format. Use ISO 8601 format (YYYY-MM-DD)'
            });
        }
    
        // Forecast period validations
        if (!input.forecast_periods || input.forecast_periods.length === 0) {
            messages.push({
                type: 'ERROR',
                message: 'At least one forecast period is required'
            });
        } else {
            input.forecast_periods.forEach((period, index) => {
                try {
                    const startDate = new Date(period.start_date);
                    const endDate = new Date(period.end_date);
    
                    if (endDate <= startDate) {
                        messages.push({
                            type: 'ERROR',
                            message: `Forecast period ${index + 1}: End date must be after start date`
                        });
                    }
    
                    if (period.quantity < 0) {
                        messages.push({
                            type: 'ERROR',
                            message: `Forecast period ${index + 1}: Quantity cannot be negative`
                        });
                    }
                } catch (e) {
                    messages.push({
                        type: 'ERROR',
                        message: `Forecast period ${index + 1}: Invalid date format`
                    });
                }
            });
        }
    
        // Batch size validations
        if (input.batch_sizes) {
            if (!input.batch_sizes.every(size => size > 0)) {
                messages.push({
                    type: 'ERROR',
                    message: 'Batch sizes must be positive numbers'
                });
            }
    
            if (!input.batch_sizes.every((size, i, arr) => i === 0 || size > arr[i - 1])) {
                messages.push({
                    type: 'ERROR',
                    message: 'Batch sizes must be in ascending order'
                });
            }
        }
    
        const hasErrors = messages.some(msg => msg.type === 'ERROR');
    
        return {
            isValid: !hasErrors,
            messages
        };
    }
Install Server

Other Tools

Related 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/brandon-butterwick/MRP_Calculation'

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