index.ts•11 kB
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
import { validateMRPInput } from './validator.js';
import { calculateMRP } from './calculator.js';
import { MRPInput } from './types.js';
class MRPCalculatorServer {
private server: Server;
constructor() {
this.server = new Server(
{
name: 'mrp-calculator',
version: '1.0.0',
},
{
capabilities: {
tools: {
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']
}
}
}
},
}
);
this.setupToolHandlers();
// Error handling
this.server.onerror = (error) => console.error('[MCP Error]', error);
process.on('SIGINT', async () => {
await this.server.close();
process.exit(0);
});
}
private setupToolHandlers() {
// Add ListToolsRequestSchema handler
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [{
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']
}
}]
}));
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,
};
}
});
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('MRP Calculator MCP server running on stdio');
}
}
const server = new MRPCalculatorServer();
server.run().catch(console.error);