Shipping & Fulfillment Tools
Shipping is where e-commerce gets physical. Your AI agent needs to calculate rates, generate labels, and track packages across multiple carriers. Let's build the tools to make this seamless.
Easyship & ShipStation MCP Servers
Both Easyship and ShipStation offer official MCP servers with access to 550+ carriers, real-time rates, and automated label generation. You can use these directly or build custom tools on their APIs.
Shipping Tool Architecture
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ AI AGENT CONVERSATION โ
โ "What shipping options do I have for this order?" โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ SHIPPING MCP TOOLS โ
โโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโค
โ calculate_ratesโ create_label โ track_shipmentโ validate_addr โ
โ get_carriers โ void_label โ schedule_pickupโ get_duties โ
โโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ MULTI-CARRIER API LAYER โ
โ โโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโ โ
โ โ USPS โ โ UPS โ โ FedEx โ โ DHL โ โ
โ โโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Core Shipping Tools
1. Calculate Shipping Rates
Get real-time rates from multiple carriers:
from mcp.server import Server
from mcp import types
import json
from datetime import datetime, timedelta
app = Server("shipping-tools")
@app.list_tools()
async def list_tools() -> list[types.Tool]:
return [
types.Tool(
name="calculate_shipping_rates",
description="""Calculates shipping rates from multiple carriers for a package.
Use when: Customer is checking out, comparing shipping options, or asking about delivery.
Returns: Array of shipping options with prices, carriers, and estimated delivery dates.
Note: Rates are live and may change - quote is valid for 30 minutes.""",
inputSchema={
"type": "object",
"properties": {
"origin": {
"type": "object",
"properties": {
"zip": {"type": "string", "pattern": "^[0-9]{5}(-[0-9]{4})?$"},
"country": {"type": "string", "default": "US"}
},
"required": ["zip"],
"description": "Ship-from address. Defaults to nearest warehouse if omitted."
},
"destination": {
"type": "object",
"properties": {
"street": {"type": "string"},
"city": {"type": "string"},
"state": {"type": "string"},
"zip": {"type": "string"},
"country": {"type": "string", "default": "US"}
},
"required": ["zip", "country"],
"description": "Ship-to address"
},
"package": {
"type": "object",
"properties": {
"weight_oz": {
"type": "number",
"minimum": 0.1,
"description": "Total weight in ounces"
},
"length_in": {"type": "number", "description": "Length in inches"},
"width_in": {"type": "number", "description": "Width in inches"},
"height_in": {"type": "number", "description": "Height in inches"}
},
"required": ["weight_oz"]
},
"service_types": {
"type": "array",
"items": {
"type": "string",
"enum": ["economy", "standard", "express", "overnight"]
},
"description": "Filter to specific service levels. Omit for all options."
},
"carriers": {
"type": "array",
"items": {
"type": "string",
"enum": ["usps", "ups", "fedex", "dhl"]
},
"description": "Filter to specific carriers. Omit for all carriers."
},
"ship_date": {
"type": "string",
"format": "date",
"description": "Date to ship (YYYY-MM-DD). Defaults to today."
}
},
"required": ["destination", "package"]
}
)
]
@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
if name == "calculate_shipping_rates":
destination = arguments["destination"]
package = arguments["package"]
ship_date = arguments.get("ship_date", datetime.now().strftime("%Y-%m-%d"))
# Determine origin (use warehouse closest to destination if not specified)
if "origin" in arguments:
origin = arguments["origin"]
else:
origin = await select_nearest_warehouse(destination["zip"])
# Get rates from shipping API (e.g., EasyPost, Shippo, ShipStation)
rates = await shipping_api.get_rates(
from_zip=origin["zip"],
from_country=origin.get("country", "US"),
to_zip=destination["zip"],
to_country=destination.get("country", "US"),
weight_oz=package["weight_oz"],
length=package.get("length_in"),
width=package.get("width_in"),
height=package.get("height_in"),
ship_date=ship_date
)
# Filter by requested carriers/services
if "carriers" in arguments:
rates = [r for r in rates if r["carrier"].lower() in arguments["carriers"]]
if "service_types" in arguments:
rates = [r for r in rates if r["service_type"] in arguments["service_types"]]
# Format response
shipping_options = []
for rate in sorted(rates, key=lambda x: x["total_cost"]):
shipping_options.append({
"carrier": rate["carrier"],
"service": rate["service_name"],
"service_type": rate["service_type"],
"price": rate["total_cost"],
"currency": "USD",
"estimated_days": rate["transit_days"],
"estimated_delivery": rate["delivery_date"],
"rate_id": rate["rate_id"], # For purchasing this rate
"tracking_included": True
})
return [types.TextContent(
type="text",
text=json.dumps({
"success": True,
"origin_zip": origin["zip"],
"destination_zip": destination["zip"],
"ship_date": ship_date,
"quote_expires": (datetime.now() + timedelta(minutes=30)).isoformat(),
"options": shipping_options
})
)]
2. Create Shipping Label
Generate a shipping label after checkout:
types.Tool(
name="create_shipping_label",
description="""Purchases a shipping label from the selected carrier.
Use when: Order is paid and ready to ship, or customer service needs to reship an item.
Returns: Label URL (PDF), tracking number, and carrier details.
Note: This charges the shipping account - use rate_id from calculate_shipping_rates.""",
inputSchema={
"type": "object",
"properties": {
"rate_id": {
"type": "string",
"description": "Rate ID from calculate_shipping_rates to purchase"
},
"order_id": {
"type": "string",
"description": "Order ID to associate with this shipment"
},
"from_address": {
"type": "object",
"properties": {
"name": {"type": "string"},
"company": {"type": "string"},
"street1": {"type": "string"},
"street2": {"type": "string"},
"city": {"type": "string"},
"state": {"type": "string"},
"zip": {"type": "string"},
"country": {"type": "string", "default": "US"},
"phone": {"type": "string"}
},
"required": ["name", "street1", "city", "state", "zip"]
},
"to_address": {
"type": "object",
"properties": {
"name": {"type": "string"},
"company": {"type": "string"},
"street1": {"type": "string"},
"street2": {"type": "string"},
"city": {"type": "string"},
"state": {"type": "string"},
"zip": {"type": "string"},
"country": {"type": "string", "default": "US"},
"phone": {"type": "string"},
"email": {"type": "string"}
},
"required": ["name", "street1", "city", "state", "zip"]
},
"package_details": {
"type": "object",
"properties": {
"weight_oz": {"type": "number"},
"length_in": {"type": "number"},
"width_in": {"type": "number"},
"height_in": {"type": "number"}
},
"required": ["weight_oz"]
},
"label_format": {
"type": "string",
"enum": ["pdf", "png", "zpl"],
"default": "pdf",
"description": "Label file format"
},
"signature_required": {
"type": "boolean",
"default": False,
"description": "Require signature on delivery"
}
},
"required": ["rate_id", "order_id", "to_address", "package_details"]
}
)
Implementation:
elif name == "create_shipping_label":
try:
# Create the shipment and purchase the label
shipment = await shipping_api.create_shipment(
rate_id=arguments["rate_id"],
from_address=arguments.get("from_address", default_warehouse_address),
to_address=arguments["to_address"],
parcel={
"weight": arguments["package_details"]["weight_oz"],
"length": arguments["package_details"].get("length_in"),
"width": arguments["package_details"].get("width_in"),
"height": arguments["package_details"].get("height_in")
},
options={
"label_format": arguments.get("label_format", "pdf"),
"signature_required": arguments.get("signature_required", False)
}
)
# Store shipment info with order
await orders_db.add_shipment(
order_id=arguments["order_id"],
shipment_id=shipment["id"],
tracking_number=shipment["tracking_number"],
carrier=shipment["carrier"],
label_url=shipment["label_url"]
)
return [types.TextContent(
type="text",
text=json.dumps({
"success": True,
"shipment_id": shipment["id"],
"order_id": arguments["order_id"],
"tracking_number": shipment["tracking_number"],
"carrier": shipment["carrier"],
"service": shipment["service"],
"label_url": shipment["label_url"],
"cost": shipment["cost"],
"estimated_delivery": shipment["estimated_delivery"]
})
)]
except ShippingAPIError as e:
return [types.TextContent(
type="text",
text=json.dumps({
"success": False,
"error": str(e),
"error_code": e.code
})
)]
3. Track Shipment
Get real-time tracking information:
types.Tool(
name="track_shipment",
description="""Gets current tracking status for a shipment.
Use when: Customer asks "where is my order?" or needs delivery status.
Returns: Current status, location, and tracking history timeline.""",
inputSchema={
"type": "object",
"properties": {
"tracking_number": {
"type": "string",
"description": "Carrier tracking number"
},
"carrier": {
"type": "string",
"enum": ["usps", "ups", "fedex", "dhl", "auto"],
"default": "auto",
"description": "Carrier name. Use 'auto' to detect from tracking number."
},
"order_id": {
"type": "string",
"description": "Alternative: Look up tracking by order ID"
}
},
"oneOf": [
{"required": ["tracking_number"]},
{"required": ["order_id"]}
]
}
)
Implementation:
elif name == "track_shipment":
# Get tracking number from order if not provided directly
if "order_id" in arguments:
shipment = await orders_db.get_shipment(arguments["order_id"])
tracking_number = shipment["tracking_number"]
carrier = shipment["carrier"]
else:
tracking_number = arguments["tracking_number"]
carrier = arguments.get("carrier", "auto")
# Fetch tracking info
tracking = await shipping_api.track(
tracking_number=tracking_number,
carrier=carrier
)
# Format tracking events
events = []
for event in tracking["events"]:
events.append({
"timestamp": event["datetime"],
"status": event["status"],
"description": event["description"],
"location": f"{event.get('city', '')}, {event.get('state', '')}".strip(", ")
})
return [types.TextContent(
type="text",
text=json.dumps({
"tracking_number": tracking_number,
"carrier": tracking["carrier"],
"status": tracking["status"],
"status_detail": tracking["status_detail"],
"estimated_delivery": tracking.get("estimated_delivery"),
"delivered_at": tracking.get("delivered_at"),
"signed_by": tracking.get("signed_by"),
"events": events
})
)]
4. Validate Address
Verify and correct shipping addresses:
types.Tool(
name="validate_address",
description="""Validates and standardizes a shipping address.
Use when: Before calculating rates or creating labels to prevent delivery issues.
Returns: Validated address with corrections, or error if invalid.""",
inputSchema={
"type": "object",
"properties": {
"street1": {"type": "string"},
"street2": {"type": "string"},
"city": {"type": "string"},
"state": {"type": "string"},
"zip": {"type": "string"},
"country": {"type": "string", "default": "US"}
},
"required": ["street1", "city", "state", "zip"]
}
)
TypeScript Implementation
import { Server } from "@modelcontextprotocol/sdk/server";
const server = new Server({ name: "shipping-tools", version: "1.0.0" });
interface ShippingRate {
carrier: string;
service: string;
service_type: string;
price: number;
transit_days: number;
delivery_date: string;
rate_id: string;
}
interface TrackingEvent {
timestamp: string;
status: string;
description: string;
location: string;
}
const shippingTools = [
{
name: "calculate_shipping_rates",
description: "Calculates real-time shipping rates from multiple carriers",
inputSchema: {
type: "object" as const,
properties: {
destination: {
type: "object",
properties: {
zip: { type: "string" },
country: { type: "string", default: "US" }
},
required: ["zip"]
},
package: {
type: "object",
properties: {
weight_oz: { type: "number", minimum: 0.1 },
length_in: { type: "number" },
width_in: { type: "number" },
height_in: { type: "number" }
},
required: ["weight_oz"]
},
service_types: {
type: "array",
items: { type: "string", enum: ["economy", "standard", "express", "overnight"] }
}
},
required: ["destination", "package"]
}
},
{
name: "create_shipping_label",
description: "Purchases a shipping label from the selected carrier",
inputSchema: {
type: "object" as const,
properties: {
rate_id: { type: "string" },
order_id: { type: "string" },
to_address: {
type: "object",
properties: {
name: { type: "string" },
street1: { type: "string" },
city: { type: "string" },
state: { type: "string" },
zip: { type: "string" },
country: { type: "string", default: "US" }
},
required: ["name", "street1", "city", "state", "zip"]
},
package_details: {
type: "object",
properties: { weight_oz: { type: "number" } },
required: ["weight_oz"]
}
},
required: ["rate_id", "order_id", "to_address", "package_details"]
}
},
{
name: "track_shipment",
description: "Gets current tracking status and history for a shipment",
inputSchema: {
type: "object" as const,
properties: {
tracking_number: { type: "string" },
carrier: { type: "string", enum: ["usps", "ups", "fedex", "dhl", "auto"], default: "auto" },
order_id: { type: "string" }
}
}
}
];
server.setRequestHandler("tools/list", async () => ({ tools: shippingTools }));
server.setRequestHandler("tools/call", async (request) => {
const { name, arguments: args } = request.params;
switch (name) {
case "calculate_shipping_rates": {
const rates = await shippingAPI.getRates({
toZip: args.destination.zip,
toCountry: args.destination.country || "US",
weightOz: args.package.weight_oz,
length: args.package.length_in,
width: args.package.width_in,
height: args.package.height_in
});
let filteredRates = rates;
if (args.service_types) {
filteredRates = rates.filter(
(r: ShippingRate) => args.service_types.includes(r.service_type)
);
}
const options = filteredRates
.sort((a: ShippingRate, b: ShippingRate) => a.price - b.price)
.map((rate: ShippingRate) => ({
carrier: rate.carrier,
service: rate.service,
service_type: rate.service_type,
price: rate.price,
currency: "USD",
estimated_days: rate.transit_days,
estimated_delivery: rate.delivery_date,
rate_id: rate.rate_id
}));
return {
content: [{
type: "text",
text: JSON.stringify({
success: true,
destination_zip: args.destination.zip,
options
})
}]
};
}
case "create_shipping_label": {
const shipment = await shippingAPI.purchaseLabel({
rateId: args.rate_id,
toAddress: args.to_address,
parcel: args.package_details
});
await ordersDB.addShipment(args.order_id, {
shipmentId: shipment.id,
trackingNumber: shipment.tracking_number,
carrier: shipment.carrier,
labelUrl: shipment.label_url
});
return {
content: [{
type: "text",
text: JSON.stringify({
success: true,
shipment_id: shipment.id,
tracking_number: shipment.tracking_number,
carrier: shipment.carrier,
label_url: shipment.label_url,
cost: shipment.cost
})
}]
};
}
case "track_shipment": {
let trackingNumber = args.tracking_number;
let carrier = args.carrier || "auto";
if (args.order_id && !trackingNumber) {
const shipment = await ordersDB.getShipment(args.order_id);
trackingNumber = shipment.trackingNumber;
carrier = shipment.carrier;
}
const tracking = await shippingAPI.track(trackingNumber, carrier);
return {
content: [{
type: "text",
text: JSON.stringify({
tracking_number: trackingNumber,
carrier: tracking.carrier,
status: tracking.status,
estimated_delivery: tracking.estimatedDelivery,
events: tracking.events.map((e: TrackingEvent) => ({
timestamp: e.timestamp,
status: e.status,
description: e.description,
location: e.location
}))
})
}]
};
}
default:
throw new Error(`Unknown tool: ${name}`);
}
});
International Shipping: Duties & Taxes
For cross-border shipments, calculate import duties and taxes:
types.Tool(
name="calculate_duties_taxes",
description="""Calculates import duties and taxes for international shipments.
Use when: Shipping outside the origin country to show total landed cost.
Returns: Duty amount, tax amount, and total de minimis threshold.""",
inputSchema={
"type": "object",
"properties": {
"destination_country": {
"type": "string",
"description": "ISO 2-letter country code (e.g., 'GB', 'DE', 'JP')"
},
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"description": {"type": "string"},
"quantity": {"type": "integer"},
"value": {"type": "number", "description": "Unit value in USD"},
"hs_code": {"type": "string", "description": "Harmonized System code"}
},
"required": ["description", "quantity", "value"]
}
},
"shipping_cost": {
"type": "number",
"description": "Shipping cost in USD"
}
},
"required": ["destination_country", "items"]
}
)
Real-World Conversation Flow
Customer: "How much is shipping to 90210?"
AI Agent: [Calls calculate_shipping_rates]
calculate_shipping_rates(
destination={"zip": "90210", "country": "US"},
package={"weight_oz": 24, "length_in": 12, "width_in": 8, "height_in": 4}
)
Tool Response:
{
"success": true,
"options": [
{"carrier": "USPS", "service": "Ground Advantage", "price": 8.95, "estimated_days": 5},
{"carrier": "UPS", "service": "Ground", "price": 12.50, "estimated_days": 4},
{"carrier": "FedEx", "service": "Express Saver", "price": 24.99, "estimated_days": 2},
{"carrier": "UPS", "service": "Next Day Air", "price": 45.00, "estimated_days": 1}
]
}
AI Agent: "Great news! Here are your shipping options to Beverly Hills:
โข **USPS Ground Advantage** - $8.95 (5 days) - Best value
โข **UPS Ground** - $12.50 (4 days)
โข **FedEx Express** - $24.99 (2 days) - Fastest economy
โข **UPS Next Day** - $45.00 (1 day) - Need it tomorrow?
Which would you prefer?"
Customer: "The FedEx 2-day please"
AI Agent: [Stores selection, proceeds to checkout]
"Perfect! I've selected FedEx Express ($24.99) for 2-day delivery.
Your order total is now $124.98. Ready to complete your purchase?"
Key Takeaways
- Multi-carrier rates give customers choices on speed vs. cost
- Validate addresses before label creation to prevent delivery issues
- Return rate_id from quotes to purchase exact quoted rate
- Track by order or tracking number for flexible lookups
- Include duties/taxes for international to avoid surprises
Next up: CRM & Customer Data Tools - Building tools to look up customers, update records, and manage communications.