We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/kuro-tomo/shopify-agentic-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server
/**
* UCP Shopping Service — Core Primitives
* Line item management and totals calculation.
* All monetary amounts are in minor units (cents).
*/
import { randomUUID } from 'node:crypto';
import type { LineItem, CheckoutTotals } from '../types.js';
/** Parameters for creating a product/service line item. */
export interface CreateLineItemParams {
product_id: string;
variant_id: string;
title: string;
quantity: number;
unit_amount: number; // minor units (cents)
type?: LineItem['type'];
image_url?: string;
sku?: string;
}
/** Validation error for a line item. */
export interface LineItemValidationError {
index: number;
field: string;
message: string;
}
/** Result of line item validation. */
export interface ValidationResult {
valid: boolean;
errors: LineItemValidationError[];
}
/** Default platform fee rate (0.5%). */
const DEFAULT_FEE_RATE = 0.005;
/** Default tax rate (8% placeholder). */
const DEFAULT_TAX_RATE = 0.08;
/**
* Creates a new line item with computed total_amount.
*/
export function createLineItem(params: CreateLineItemParams): LineItem {
const {
product_id,
variant_id,
title,
quantity,
unit_amount,
type = 'product',
image_url,
sku,
} = params;
if (quantity <= 0) {
throw new Error('Line item quantity must be greater than zero.');
}
if (unit_amount < 0) {
throw new Error('Line item unit_amount must not be negative.');
}
const total_amount = unit_amount * quantity;
return {
id: randomUUID(),
product_id,
variant_id,
title,
quantity,
unit_amount,
total_amount,
type,
...(image_url !== undefined && { image_url }),
...(sku !== undefined && { sku }),
};
}
/**
* Calculates checkout totals from a list of line items.
*
* Breakdown:
* - subtotal: sum of product + service line item totals
* - tax: subtotal * taxRate (default 8%)
* - shipping: provided externally (defaults to 0)
* - discount: absolute sum of discount-type line items
* - fee: subtotal * feeRate (default 0.5%)
* - total: subtotal + tax + shipping - discount + fee
*/
export function calculateTotals(
lineItems: LineItem[],
currency: string,
options: {
shippingAmount?: number;
taxRate?: number;
feeRate?: number;
} = {},
): CheckoutTotals {
const {
shippingAmount = 0,
taxRate = DEFAULT_TAX_RATE,
feeRate = DEFAULT_FEE_RATE,
} = options;
const subtotal = lineItems
.filter((li) => li.type === 'product' || li.type === 'service')
.reduce((sum, li) => sum + li.total_amount, 0);
const tax = Math.round(subtotal * taxRate);
const shipping = shippingAmount;
const discount = Math.abs(
lineItems
.filter((li) => li.type === 'discount')
.reduce((sum, li) => sum + li.total_amount, 0),
);
const fee = Math.round(subtotal * feeRate);
const total = subtotal + tax + shipping - discount + fee;
return {
subtotal,
tax,
shipping,
discount,
fee,
total,
currency,
};
}
/**
* Validates an array of line items for correctness.
*
* Checks:
* - quantity > 0
* - unit_amount >= 0
* - total_amount == unit_amount * quantity
* - required string fields are non-empty
* - type is a recognized value
*/
export function validateLineItems(lineItems: LineItem[]): ValidationResult {
const errors: LineItemValidationError[] = [];
const validTypes: LineItem['type'][] = ['product', 'service', 'tax', 'discount', 'fee'];
lineItems.forEach((item, index) => {
if (!item.id || item.id.trim() === '') {
errors.push({ index, field: 'id', message: 'Line item id is required.' });
}
if (!item.product_id || item.product_id.trim() === '') {
errors.push({ index, field: 'product_id', message: 'Product id is required.' });
}
if (!item.variant_id || item.variant_id.trim() === '') {
errors.push({ index, field: 'variant_id', message: 'Variant id is required.' });
}
if (!item.title || item.title.trim() === '') {
errors.push({ index, field: 'title', message: 'Title is required.' });
}
if (item.quantity <= 0) {
errors.push({ index, field: 'quantity', message: 'Quantity must be greater than zero.' });
}
if (item.unit_amount < 0) {
errors.push({ index, field: 'unit_amount', message: 'Unit amount must not be negative.' });
}
const expectedTotal = item.unit_amount * item.quantity;
if (item.total_amount !== expectedTotal) {
errors.push({
index,
field: 'total_amount',
message: `Total amount (${item.total_amount}) does not match unit_amount * quantity (${expectedTotal}).`,
});
}
if (!validTypes.includes(item.type)) {
errors.push({
index,
field: 'type',
message: `Invalid type "${item.type}". Must be one of: ${validTypes.join(', ')}.`,
});
}
});
return {
valid: errors.length === 0,
errors,
};
}