import { Tool } from '@modelcontextprotocol/sdk/types.js';
import { SFExpressClient } from '../sf-express-client.js';
import {
LogisticsServicesRequestSchema,
LogisticsServicesRequest,
SFExpressError
} from '../types.js';
export const logisticsServicesTool: Tool = {
name: 'sf_express_logistics_services',
description: 'Query SF Express logistics services including warehousing, distribution, fulfillment, and return services.',
inputSchema: {
type: 'object',
properties: {
serviceType: {
type: 'string',
enum: ['warehouse', 'distribution', 'fulfillment', 'return'],
description: 'Type of logistics service: warehouse-Warehousing, distribution-Distribution, fulfillment-Order fulfillment, return-Return processing'
},
locationCode: {
type: 'string',
description: 'Service location code or area code where logistics services are needed'
},
requirements: {
type: 'object',
description: 'Specific requirements for the logistics service (optional)',
properties: {
storageType: {
type: 'string',
description: 'Type of storage needed (e.g., general, cold-chain, hazardous)'
},
capacity: {
type: 'number',
description: 'Required storage or processing capacity',
minimum: 0
},
specialServices: {
type: 'array',
description: 'List of special services required',
items: {
type: 'string'
}
}
}
}
},
required: ['serviceType', 'locationCode']
}
};
export async function handleLogisticsServices(
args: any,
client: SFExpressClient
): Promise<any> {
try {
// Validate input using Zod schema
const validatedRequest = LogisticsServicesRequestSchema.parse(args);
// Call SF Express API
const result = await client.getLogisticsServices(validatedRequest);
// Format the response with enhanced service information
const formattedServices = result.services.map(service => ({
serviceId: service.serviceId,
serviceName: service.serviceName,
serviceType: service.serviceType,
location: service.location,
availability: {
available: service.available,
status: service.available ? 'Available' : 'Not Available'
},
capacity: {
total: service.capacity,
unit: getCapacityUnit(validatedRequest.serviceType)
},
pricing: service.pricing ? {
basePrice: service.pricing.basePrice,
currency: service.pricing.currency,
priceModel: service.pricing.priceModel,
description: getPricingDescription(service.pricing.priceModel)
} : null,
features: service.features || [],
suitability: assessSuitability(service, validatedRequest.requirements)
}));
// Filter and sort services by availability and suitability
const availableServices = formattedServices
.filter(s => s.availability.available)
.sort((a, b) => b.suitability.score - a.suitability.score);
const unavailableServices = formattedServices.filter(s => !s.availability.available);
return {
success: true,
data: {
query: {
serviceType: validatedRequest.serviceType,
location: result.locationCode,
requirements: validatedRequest.requirements
},
summary: {
totalServices: formattedServices.length,
availableServices: availableServices.length,
unavailableServices: unavailableServices.length,
recommendedService: availableServices.length > 0 ? availableServices[0].serviceId : null
},
services: {
available: availableServices,
unavailable: unavailableServices
},
message: `Found ${availableServices.length} available logistics service(s) in ${result.locationCode}`
}
};
} catch (error) {
if (error instanceof SFExpressError) {
return {
success: false,
error: {
code: error.errorCode || 'LOGISTICS_SERVICES_ERROR',
message: error.message,
details: error.details
}
};
}
// Handle Zod validation errors
if (error.name === 'ZodError') {
return {
success: false,
error: {
code: 'VALIDATION_ERROR',
message: 'Invalid input parameters',
details: error.errors
}
};
}
return {
success: false,
error: {
code: 'UNKNOWN_ERROR',
message: 'An unexpected error occurred while querying logistics services',
details: error instanceof Error ? error.message : 'Unknown error'
}
};
}
}
/**
* Get the appropriate capacity unit based on service type
*/
function getCapacityUnit(serviceType: string): string {
switch (serviceType) {
case 'warehouse':
return 'cubic meters';
case 'distribution':
return 'packages per day';
case 'fulfillment':
return 'orders per day';
case 'return':
return 'returns per day';
default:
return 'units';
}
}
/**
* Get pricing model description
*/
function getPricingDescription(priceModel: string): string {
switch (priceModel.toLowerCase()) {
case 'fixed':
return 'Fixed monthly/annual fee';
case 'usage':
return 'Pay per usage (volume-based)';
case 'tiered':
return 'Tiered pricing based on volume';
case 'hybrid':
return 'Base fee plus usage charges';
default:
return 'Contact for pricing details';
}
}
/**
* Assess how well a service matches the requirements
*/
function assessSuitability(service: any, requirements?: any): { score: number; reasons: string[] } {
let score = 50; // Base score
const reasons: string[] = [];
if (!requirements) {
return { score, reasons: ['No specific requirements provided'] };
}
// Check storage type match
if (requirements.storageType && service.features) {
const hasStorageType = service.features.some((feature: string) =>
feature.toLowerCase().includes(requirements.storageType.toLowerCase())
);
if (hasStorageType) {
score += 20;
reasons.push(`Supports ${requirements.storageType} storage`);
} else {
score -= 10;
reasons.push(`May not support ${requirements.storageType} storage`);
}
}
// Check capacity match
if (requirements.capacity && service.capacity) {
if (service.capacity >= requirements.capacity) {
score += 15;
reasons.push(`Has sufficient capacity (${service.capacity} >= ${requirements.capacity})`);
} else {
score -= 15;
reasons.push(`Insufficient capacity (${service.capacity} < ${requirements.capacity})`);
}
}
// Check special services match
if (requirements.specialServices && requirements.specialServices.length > 0) {
const matchedServices = requirements.specialServices.filter((reqService: string) =>
service.features?.some((feature: string) =>
feature.toLowerCase().includes(reqService.toLowerCase())
)
);
if (matchedServices.length > 0) {
score += matchedServices.length * 5;
reasons.push(`Supports ${matchedServices.length} of ${requirements.specialServices.length} special services`);
}
}
return { score: Math.max(0, Math.min(100, score)), reasons };
}