get_amazon_orders
Fetch Amazon order history with summaries, item details, and shipment tracking for specified date ranges or years to create reports or browse past purchases.
Instructions
Fetch Amazon order history for a specified date range or year. Returns order summaries including: order ID, date, total amount, status, item count, shipping address (7 lines), payment method, and Subscribe & Save frequency. Optionally includes detailed item data (ASIN, name, price, quantity, seller, condition) and shipment tracking. Use for browsing order history or building reports.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| region | Yes | Amazon region code. Supported: us, uk, ca, de, fr, es, it, nl, jp, au, mx, in, ae, sa, ie, be | |
| year | No | Year to fetch orders from (e.g., 2024). If omitted, uses current year. | |
| start_date | No | Start date in ISO format (YYYY-MM-DD). Overrides year if provided. | |
| end_date | No | End date in ISO format (YYYY-MM-DD). Overrides year if provided. | |
| include_items | No | Extract item details (ASIN, name, price, quantity, seller, condition) from each order's invoice page. Adds ~2s per order. | |
| include_shipments | No | Extract shipment info (delivery status, tracking link) from each order's detail page. Adds ~2s per order. Note: tracking link URL is captured but not the carrier tracking number - use fetch_tracking_numbers for that. | |
| fetch_tracking_numbers | No | Extract actual carrier tracking numbers (e.g., AZ218181365JE) by visiting each shipment's 'Track package' page. Adds ~2s per shipment. Only works when include_shipments is true. | |
| max_orders | No | Maximum number of orders to fetch. Use to limit results for large accounts or avoid timeouts. |
Implementation Reference
- src/index.ts:133-186 (schema)Tool schema definition including name, description, and detailed inputSchema with parameters for region, date range, item/shipment inclusion options{ name: "get_amazon_orders", description: "Fetch Amazon order history for a specified date range or year. Returns order summaries including: order ID, date, total amount, status, item count, shipping address (7 lines), payment method, and Subscribe & Save frequency. Optionally includes detailed item data (ASIN, name, price, quantity, seller, condition) and shipment tracking. Use for browsing order history or building reports.", inputSchema: { type: "object", properties: { region: { type: "string", description: `Amazon region code. Supported: ${getRegionCodes().join(", ")}`, enum: getRegionCodes(), }, year: { type: "number", description: "Year to fetch orders from (e.g., 2024). If omitted, uses current year.", }, start_date: { type: "string", description: "Start date in ISO format (YYYY-MM-DD). Overrides year if provided.", }, end_date: { type: "string", description: "End date in ISO format (YYYY-MM-DD). Overrides year if provided.", }, include_items: { type: "boolean", description: "Extract item details (ASIN, name, price, quantity, seller, condition) from each order's invoice page. Adds ~2s per order.", default: false, }, include_shipments: { type: "boolean", description: "Extract shipment info (delivery status, tracking link) from each order's detail page. Adds ~2s per order. Note: tracking link URL is captured but not the carrier tracking number - use fetch_tracking_numbers for that.", default: false, }, fetch_tracking_numbers: { type: "boolean", description: "Extract actual carrier tracking numbers (e.g., AZ218181365JE) by visiting each shipment's 'Track package' page. Adds ~2s per shipment. Only works when include_shipments is true.", default: false, }, max_orders: { type: "number", description: "Maximum number of orders to fetch. Use to limit results for large accounts or avoid timeouts.", }, }, required: ["region"], }, },
- src/index.ts:648-750 (handler)MCP tool call handler for get_amazon_orders: validates region, calls fetchOrders with parameters, processes result, and returns JSON response with order datacase "get_amazon_orders": { const regionParam = args?.region as string | undefined; const regionError = validateRegion(regionParam, args); if (regionError) return regionError; const region = regionParam!; // Validated above // Get progress token from request meta const progressToken = request.params._meta?.progressToken; const currentPage = await getPage(); const result = await fetchOrders(currentPage, amazonPlugin, { region, year: args?.year as number | undefined, startDate: args?.start_date as string | undefined, endDate: args?.end_date as string | undefined, includeItems: args?.include_items as boolean | undefined, includeShipments: args?.include_shipments as boolean | undefined, fetchTrackingNumbers: args?.fetch_tracking_numbers as | boolean | undefined, maxOrders: args?.max_orders as number | undefined, onProgress: async (message, current, total) => { await sendProgress(progressToken, current, total, message); }, }); return { content: [ { type: "text", text: JSON.stringify( { status: "success", // Echo input parameters for debugging params: { region, year: args?.year, startDate: args?.start_date, endDate: args?.end_date, includeItems: args?.include_items, includeShipments: args?.include_shipments, fetchTrackingNumbers: args?.fetch_tracking_numbers, maxOrders: args?.max_orders, }, totalOrders: result.totalFound, orders: result.orders.map((o) => ({ id: o.id, date: o.date?.toISOString(), total: o.total, status: o.status?.label || "Unknown", // Use itemCount from order header (extracted from list page) or fall back to items array length itemCount: o.itemCount ?? o.items?.length ?? 0, shipmentCount: o.shipments?.length || 0, // Enhanced order header data from list page subtotal: o.subtotal, shipping: o.shipping, tax: o.tax, vat: o.vat, promotion: o.promotion, grandTotal: o.grandTotal, // Shipping address shippingAddress: o.shippingAddress, // Payment method from list page paymentMethod: o.paymentMethod, // Recipient (simple name) recipient: typeof o.recipient === "object" ? o.recipient : { name: o.recipient }, // Payments from detail/invoice payments: o.payments, // Include items when extracted items: o.items?.map((i) => ({ name: i.name, asin: i.asin, quantity: i.quantity, unitPrice: i.unitPrice, condition: i.condition, seller: i.seller?.name, subscriptionFrequency: i.subscriptionFrequency, })), // Include shipments when extracted shipments: o.shipments?.map((s) => ({ shipmentId: s.shipmentId, status: s.status, delivered: s.delivered, trackingId: s.trackingId, carrier: s.carrier, trackingLink: s.trackingLink, itemCount: s.items?.length || 0, })), })), itemCount: result.items.length, shipmentCount: result.shipments.length, errors: result.errors, }, null, 2, ), }, ], }; }
- src/tools/fetch-orders.ts:101-948 (handler)Core fetchOrders function implementing the tool logic: orchestrates browser navigation to Amazon order pages, extracts order headers from list pages with pagination, fetches detailed item/shipment/transaction data from invoice/detail pages, supports single-order mode, progress reporting, and various optionsexport async function fetchOrders( page: Page, plugin: AmazonPlugin, options: FetchOrdersOptions, ): Promise<FetchOrdersResult> { const { region, year, startDate, endDate, includeItems = false, includeShipments = false, includeTransactions = false, fetchTrackingNumbers = false, useInvoice = true, // Default to invoice extraction (faster) maxOrders, orderId, onProgress, } = options; const result: FetchOrdersResult = { orders: [], items: [], shipments: [], transactions: [], totalFound: 0, errors: [], }; const regionConfig = getRegionByCode(region); if (!regionConfig) { result.errors.push( `Unknown region: ${region}. Valid regions: us, uk, ca, de, fr, es, it, nl, jp, au, mx, in, ae, sa, ie, be`, ); return result; } const domain = regionConfig.domain; const currency = regionConfig.currency || "USD"; const currencySymbol = currency === "GBP" ? "£" : currency === "EUR" ? "€" : "$"; try { // If specific orderId requested, skip order list and go directly to invoice/detail if (orderId) { console.error( `[fetch-orders] Fetching single order: ${orderId} (region: ${region})`, ); onProgress?.(`Fetching order ${orderId}...`, 0, 1); // Create header for this order const header: OrderHeader = { id: orderId, orderId, date: null, total: { amount: 0, currency, currencySymbol, formatted: "" }, detailUrl: `https://www.${domain}/gp/your-account/order-details?orderID=${orderId}`, platform: "amazon", region, }; const enrichedOrder: EnrichedOrder = { ...header }; // Go to invoice page and extract items directly (inline, with timeouts) const invoiceUrl = `https://www.${domain}/gp/css/summary/print.html?orderID=${orderId}`; console.error(`[fetch-orders] Navigating to invoice: ${invoiceUrl}`); await page.goto(invoiceUrl, { waitUntil: "domcontentloaded", timeout: 15000, }); const currentUrl = page.url(); console.error(`[fetch-orders] Current URL: ${currentUrl}`); await page .waitForSelector('[data-component="purchasedItems"], table, .a-box', { timeout: 3000, }) .catch(() => {}); // Check for error banners (e.g., "We're unable to load your order details") const errorBanner = await page .locator( '[data-component="errorbanner"], .a-alert-error, .a-alert-info', ) .first(); const errorBannerCount = await errorBanner.count().catch(() => 0); if (errorBannerCount > 0) { const errorText = await errorBanner .textContent({ timeout: 500 }) .catch(() => ""); if ( errorText?.includes("unable to load") || errorText?.includes("problem loading") ) { console.error( `[fetch-orders] Error detected: ${errorText.slice(0, 200)}`, ); result.errors.push( `Order page error: ${errorText.slice(0, 200).trim()}`, ); } } // Try to extract items - first try data-component, then fall back to extractFromInvoice const itemContainers = await page .locator('[data-component="purchasedItems"]') .all(); console.error( `[fetch-orders] Found ${itemContainers.length} purchasedItems containers on invoice page`, ); // Invoice pages often don't have data-component, try the full invoice extractor if (itemContainers.length === 0) { console.error( `[fetch-orders] No data-component items found, using extractFromInvoice`, ); const invoiceData = await extractFromInvoice(page, header); // Copy over invoice data (amounts, recipient, payments) regardless of items console.error( `[fetch-orders] Invoice data: subtotal=${invoiceData.subtotal?.formatted}, total=${invoiceData.total?.formatted}, vat=${invoiceData.vat?.formatted}, shipping=${invoiceData.shipping?.formatted}`, ); if (invoiceData.subtotal) enrichedOrder.subtotal = invoiceData.subtotal; if (invoiceData.total) enrichedOrder.grandTotal = invoiceData.total; if (invoiceData.shipping) enrichedOrder.shipping = invoiceData.shipping; if (invoiceData.tax) enrichedOrder.tax = invoiceData.tax; if (invoiceData.vat) enrichedOrder.vat = invoiceData.vat; if (invoiceData.gift) enrichedOrder.promotion = invoiceData.gift; if (invoiceData.recipientName) { enrichedOrder.recipient = invoiceData.recipientName; if (invoiceData.shippingAddress) { // Prepend recipient name as line1 if address doesn't start with it const addressWithName = [ invoiceData.recipientName, ...invoiceData.shippingAddress, ]; enrichedOrder.shippingAddress = parseInvoiceAddressLines(addressWithName); } } if (invoiceData.payments && invoiceData.payments.length > 0) { enrichedOrder.payments = invoiceData.payments; // Also set paymentMethod from first payment const firstPayment = invoiceData.payments[0]; enrichedOrder.paymentMethod = { type: firstPayment.method, lastFour: firstPayment.lastFour, }; } // Convert invoice items to full Item type if found if (invoiceData.items && invoiceData.items.length > 0) { // Create enriched header with all order data for items const enrichedHeader: OrderHeader = { ...header, recipient: typeof enrichedOrder.recipient === "string" ? enrichedOrder.recipient : undefined, subtotal: enrichedOrder.subtotal, shipping: enrichedOrder.shipping, tax: enrichedOrder.tax, vat: enrichedOrder.vat, promotion: enrichedOrder.promotion, grandTotal: enrichedOrder.grandTotal, shippingAddress: enrichedOrder.shippingAddress, paymentMethod: enrichedOrder.paymentMethod, }; const items = invoiceData.items.map((ii) => ({ id: ii.asin || ii.name.slice(0, 50), asin: ii.asin, name: ii.name, quantity: ii.quantity, unitPrice: ii.unitPrice, totalPrice: { ...ii.unitPrice, amount: ii.unitPrice.amount * ii.quantity, formatted: `${currencySymbol}${(ii.unitPrice.amount * ii.quantity).toFixed(2)}`, }, url: ii.asin ? `https://www.${domain}/dp/${ii.asin}` : "", orderHeader: enrichedHeader, condition: ii.condition, seller: ii.seller ? { name: ii.seller } : undefined, subscriptionFrequency: ii.subscriptionFrequency, platformData: { source: "invoice" }, })); console.error( `[fetch-orders] extractFromInvoice found ${items.length} items`, ); enrichedOrder.items = items; result.items = items; } } // Only use inline extraction if we found data-component containers (detail page) // Otherwise extractFromInvoice already handled it above if (itemContainers.length > 0) { // Create enriched header with all order data for items const enrichedHeader: OrderHeader = { ...header, recipient: typeof enrichedOrder.recipient === "string" ? enrichedOrder.recipient : undefined, subtotal: enrichedOrder.subtotal, shipping: enrichedOrder.shipping, tax: enrichedOrder.tax, vat: enrichedOrder.vat, promotion: enrichedOrder.promotion, grandTotal: enrichedOrder.grandTotal, shippingAddress: enrichedOrder.shippingAddress, paymentMethod: enrichedOrder.paymentMethod, }; const extractedItems: Item[] = []; for (const container of itemContainers) { try { // Title + ASIN const titleLink = container .locator('[data-component="itemTitle"] a') .first(); const titleCount = await titleLink.count().catch(() => 0); if (titleCount === 0) continue; const name = await titleLink .textContent({ timeout: 500 }) .catch(() => ""); if (!name?.trim()) continue; const href = await titleLink .getAttribute("href", { timeout: 500 }) .catch(() => ""); const asinMatch = href?.match(/\/dp\/([A-Z0-9]+)/i); const asin = asinMatch ? asinMatch[1] : undefined; // Price const priceEl = container .locator('[data-component="unitPrice"] .a-offscreen') .first(); const priceText = await priceEl .textContent({ timeout: 500 }) .catch(() => ""); const priceMatch = priceText?.match(/[£$€]?([\d,.]+)/); const priceAmount = priceMatch ? parseFloat(priceMatch[1].replace(",", "")) : 0; // Quantity (check badge first, then quantity component) let quantity = 1; const qtyBadge = container .locator(".od-item-view-qty span") .first(); const qtyBadgeCount = await qtyBadge.count().catch(() => 0); if (qtyBadgeCount > 0) { const qtyText = await qtyBadge .textContent({ timeout: 500 }) .catch(() => ""); const qtyMatch = qtyText?.match(/(\d+)/); if (qtyMatch) quantity = parseInt(qtyMatch[1], 10); } // Seller let seller: string | undefined; const sellerEl = container .locator('[data-component="orderedMerchant"]') .first(); const sellerCount = await sellerEl.count().catch(() => 0); if (sellerCount > 0) { const sellerText = await sellerEl .textContent({ timeout: 500 }) .catch(() => ""); const sellerMatch = sellerText?.match(/Sold by:\s*(.+)/i); if (sellerMatch) seller = sellerMatch[1].trim(); } // Condition let condition: string | undefined; const condEl = container .locator('[data-component="itemCondition"]') .first(); const condCount = await condEl.count().catch(() => 0); if (condCount > 0) { const condText = await condEl .textContent({ timeout: 500 }) .catch(() => ""); const condMatch = condText?.match(/Condition:\s*(.+)/i); if (condMatch) condition = condMatch[1].trim(); } // Subscription frequency let subscriptionFrequency: string | undefined; const freqEl = container .locator('[data-component="deliveryFrequency"]') .first(); const freqCount = await freqEl.count().catch(() => 0); if (freqCount > 0) { const freqText = await freqEl .textContent({ timeout: 500 }) .catch(() => ""); const freqMatch = freqText?.match(/Auto-delivered:\s*(.+)/i); if (freqMatch) subscriptionFrequency = freqMatch[1].trim(); } extractedItems.push({ id: asin || name.trim().slice(0, 50), asin, name: name.trim(), quantity, unitPrice: { amount: priceAmount, currency, currencySymbol, formatted: `${currencySymbol}${priceAmount.toFixed(2)}`, }, totalPrice: { amount: priceAmount * quantity, currency, currencySymbol, formatted: `${currencySymbol}${(priceAmount * quantity).toFixed(2)}`, }, url: asin ? `https://www.${domain}/dp/${asin}` : "", orderHeader: enrichedHeader, condition, seller: seller ? { name: seller } : undefined, subscriptionFrequency, platformData: { source: "invoice" }, }); } catch { continue; } } console.error( `[fetch-orders] Extracted ${extractedItems.length} items from data-component`, ); // Store items if (extractedItems.length > 0) { enrichedOrder.items = extractedItems; result.items = extractedItems; } } // If no items found from invoice, try the detail page if (result.items.length === 0 && result.errors.length === 0) { console.error( `[fetch-orders] No items from invoice, trying detail page extraction`, ); await page.goto(header.detailUrl, { waitUntil: "domcontentloaded", timeout: 15000, }); await page .waitForSelector('[data-component="purchasedItems"], .a-box', { timeout: 2000, }) .catch(() => {}); // Check for error banners on detail page const detailErrorBanner = await page .locator( '[data-component="errorbanner"], .a-alert-error, .a-alert-info', ) .first(); const detailErrorCount = await detailErrorBanner.count().catch(() => 0); if (detailErrorCount > 0) { const errorText = await detailErrorBanner .textContent({ timeout: 500 }) .catch(() => ""); if ( errorText?.includes("unable to load") || errorText?.includes("problem loading") ) { console.error( `[fetch-orders] Detail page error: ${errorText.slice(0, 200)}`, ); result.errors.push( `Order detail error: ${errorText.slice(0, 200).trim()}`, ); } } if (result.errors.length === 0) { const items = await plugin.extractItems(page, header).catch(() => []); if (items.length > 0) { console.error( `[fetch-orders] Found ${items.length} items from detail page`, ); enrichedOrder.items = items; result.items = items; } } } // Get shipments from detail page if requested if (includeShipments) { console.error(`[fetch-orders] Fetching shipments from detail page`); // Only navigate if not already there if (!page.url().includes("order-details")) { await page.goto(header.detailUrl, { waitUntil: "domcontentloaded", timeout: 15000, }); await page .waitForSelector('[data-component="shipments"], .a-box', { timeout: 2000, }) .catch(() => {}); } const shipments = await plugin .extractShipments(page, header, fetchTrackingNumbers) .catch(() => []); enrichedOrder.shipments = shipments; result.shipments = shipments; console.error(`[fetch-orders] Found ${shipments.length} shipments`); } // Get transactions if requested if (includeTransactions) { const transactions = await plugin .extractTransactions(page, header) .catch(() => []); result.transactions = transactions; console.error( `[fetch-orders] Found ${transactions.length} transactions`, ); } result.orders = [enrichedOrder]; result.totalFound = 1; return result; } // Build order list URL (for multi-order fetch) const listUrl = plugin.getOrderListUrl(region, { year, startDate: startDate ? new Date(startDate) : undefined, endDate: endDate ? new Date(endDate) : undefined, }); // Navigate to order list console.error(`[fetch-orders] Navigating to: ${listUrl}`); onProgress?.("Navigating to order history...", 0, 0); await page.goto(listUrl, { waitUntil: "domcontentloaded", timeout: 60000 }); // Wait for order cards to appear (more reliable than fixed timeout) await page .waitForSelector('.order-card, [class*="order-card"], .a-box-group', { timeout: 3000, }) .catch(() => {}); console.error(`[fetch-orders] Page loaded, URL: ${page.url()}`); // Check authentication console.error(`[fetch-orders] Checking auth...`); const authStatus = await plugin.checkAuthStatus(page, region); console.error(`[fetch-orders] Auth result: ${JSON.stringify(authStatus)}`); if (!authStatus.authenticated) { result.errors.push(`Not authenticated: ${authStatus.message}`); return result; } // Extract orders from all pages let pageNum = 1; let hasMore = true; while (hasMore) { console.error(`[fetch-orders] Extracting page ${pageNum}...`); onProgress?.( `Extracting orders from page ${pageNum}...`, result.orders.length, 0, ); // Extract order headers from current page const pageHeaders = await extractOrderHeaders(page, region); console.error( `[fetch-orders] Found ${pageHeaders.length} orders on page ${pageNum}`, ); // Cast to the enriched type for result storage result.orders.push(...(pageHeaders as EnrichedOrder[])); onProgress?.( `Found ${result.orders.length} orders (page ${pageNum})...`, result.orders.length, 0, ); // Check if we've hit the max if (maxOrders && result.orders.length >= maxOrders) { result.orders = result.orders.slice(0, maxOrders); break; } // Check for next page hasMore = await hasNextPage(page); console.error(`[fetch-orders] Has next page: ${hasMore}`); if (hasMore && result.orders.length < (maxOrders || 1000)) { const navigated = await goToNextPage(page); console.error(`[fetch-orders] Navigated to next: ${navigated}`); if (!navigated) break; pageNum++; } else { hasMore = false; } } result.totalFound = result.orders.length; const extractionMode = useInvoice ? "invoice" : "detail"; onProgress?.( `Found ${result.totalFound} orders, starting ${extractionMode} extraction...`, 0, result.totalFound, ); // If detailed extraction requested, visit each order if (includeItems || includeShipments || includeTransactions) { const startTime = Date.now(); let processedCount = 0; for (let i = 0; i < result.orders.length; i++) { const order = result.orders[i]; // Calculate ETA based on average time per order const elapsed = Date.now() - startTime; const avgTimePerOrder = processedCount > 0 ? elapsed / processedCount : useInvoice ? 1500 : 3000; const remaining = result.orders.length - i; const etaSeconds = Math.round((avgTimePerOrder * remaining) / 1000); const etaStr = etaSeconds > 60 ? `~${Math.round(etaSeconds / 60)}m ${etaSeconds % 60}s` : `~${etaSeconds}s`; console.error( `[fetch-orders] Processing order ${i + 1}/${result.orders.length}: ${order.id} (${extractionMode} mode)`, ); onProgress?.( `Order ${i + 1}/${result.orders.length} (${order.id}) - ETA: ${etaStr}`, i + 1, result.orders.length, ); // Skip cancelled orders - they have no useful detail to extract const orderStatus = order.status?.label?.toLowerCase() || ""; if (orderStatus === "cancelled") { console.error(`[fetch-orders] Skipping cancelled order ${order.id}`); order.items = []; processedCount++; continue; } // Create header for extraction functions let recipientStr: string | undefined; if (typeof order.recipient === "string") { recipientStr = order.recipient; } else if ( order.recipient && typeof order.recipient === "object" && "name" in order.recipient ) { recipientStr = (order.recipient as { name: string }).name; } const header: OrderHeader = { id: order.id, orderId: order.orderId, date: order.date, total: order.total, detailUrl: order.detailUrl, recipient: recipientStr, platform: order.platform, region: order.region, }; try { if (useInvoice) { // Invoice-based extraction (faster, cleaner HTML) console.error( `[fetch-orders] Using invoice extraction for ${order.id}`, ); onProgress?.( `Order ${i + 1}/${result.orders.length} - Loading invoice...`, i, result.orders.length, ); const invoiceData = await extractFromInvoice(page, header); // Merge all invoice data into order (amounts, recipient, payments) if (invoiceData.subtotal) order.subtotal = invoiceData.subtotal; if (invoiceData.shipping) order.shipping = invoiceData.shipping; if (invoiceData.tax) order.tax = invoiceData.tax; if (invoiceData.vat) order.vat = invoiceData.vat; if (invoiceData.total) order.grandTotal = invoiceData.total; if (invoiceData.gift) order.promotion = invoiceData.gift; if (invoiceData.payments && invoiceData.payments.length > 0) { order.payments = invoiceData.payments; // Also set paymentMethod from first payment const firstPayment = invoiceData.payments[0]; order.paymentMethod = { type: firstPayment.method, lastFour: firstPayment.lastFour, }; } if (invoiceData.recipientName) { order.recipient = invoiceData.recipientName; if (invoiceData.shippingAddress) { // Prepend recipient name as line1 if address doesn't start with it const addressWithName = [ invoiceData.recipientName, ...invoiceData.shippingAddress, ]; order.shippingAddress = parseInvoiceAddressLines(addressWithName); } } // Extract items from invoice if requested if (includeItems) { // Create enriched header with all order data for items // This includes shippingAddress, status, etc. that were merged above const enrichedHeader: OrderHeader = { id: order.id, orderId: order.orderId, date: order.date, total: order.total, detailUrl: order.detailUrl, recipient: recipientStr, status: order.status, platform: order.platform, region: order.region, subtotal: order.subtotal, shipping: order.shipping, tax: order.tax, vat: order.vat, promotion: order.promotion, grandTotal: order.grandTotal, shippingAddress: order.shippingAddress, paymentMethod: order.paymentMethod, subscribeAndSave: order.subscribeAndSave, }; // Check if invoice has items and matches expected count from order list const expectedItemCount = order.itemCount || 0; const invoiceItemCount = invoiceData.items?.length || 0; if ( invoiceData.items && invoiceItemCount > 0 && (expectedItemCount === 0 || invoiceItemCount >= expectedItemCount) ) { // Invoice has items and count looks correct - use invoice data const items = invoiceData.items.map((ii) => ({ id: ii.asin || ii.name.slice(0, 50), asin: ii.asin, name: ii.name, quantity: ii.quantity, unitPrice: ii.unitPrice, totalPrice: { ...ii.unitPrice, amount: ii.unitPrice.amount * ii.quantity, }, url: ii.asin ? `https://www.${regionConfig.domain}/dp/${ii.asin}` : "", orderHeader: enrichedHeader, condition: ii.condition, seller: ii.seller ? { name: ii.seller } : undefined, subscriptionFrequency: ii.subscriptionFrequency, platformData: { source: "invoice" }, })); console.error( `[fetch-orders] Found ${items.length} items from invoice (expected ${expectedItemCount})`, ); result.items.push(...items); order.items = items; } else { // Fallback to detail page if: // - Invoice extraction found no items, OR // - Invoice item count doesn't match expected count from order list console.error( `[fetch-orders] Invoice has ${invoiceItemCount} items but expected ${expectedItemCount}, falling back to detail page`, ); await page.goto(order.detailUrl, { waitUntil: "domcontentloaded", timeout: 15000, }); await page .waitForSelector('.a-box, [data-component="orderDetails"]', { timeout: 1000, }) .catch(() => {}); const items = await plugin .extractItems(page, enrichedHeader) .catch(() => []); console.error( `[fetch-orders] Found ${items.length} items from detail page`, ); result.items.push(...items); order.items = items; } } // Shipments need detail page (not on invoice) if (includeShipments) { console.error( `[fetch-orders] Fetching shipments from detail page`, ); onProgress?.( `Order ${i + 1}/${result.orders.length} - Fetching shipments...`, i, result.orders.length, ); await page.goto(order.detailUrl, { waitUntil: "domcontentloaded", timeout: 15000, }); await page .waitForSelector( '[data-component="shipments"], .shipment-is-delivered, .a-box', { timeout: 1000 }, ) .catch(() => {}); const shipments = await plugin .extractShipments(page, header, fetchTrackingNumbers) .catch(() => []); result.shipments.push(...shipments); order.shipments = shipments; } // Transactions from detail page if (includeTransactions) { if (!includeShipments) { await page.goto(order.detailUrl, { waitUntil: "domcontentloaded", timeout: 15000, }); } const transactions = await plugin .extractTransactions(page, header) .catch(() => []); result.transactions.push(...transactions); } processedCount++; } else { // Detail page extraction (original method) console.error(`[fetch-orders] Navigating to: ${order.detailUrl}`); onProgress?.( `Order ${i + 1}/${result.orders.length} - Loading details...`, i, result.orders.length, ); await page.goto(order.detailUrl, { waitUntil: "domcontentloaded", timeout: 30000, }); await page .waitForSelector( '#od-subtotals, [data-component="orderDetails"], .order-details, .a-box', { timeout: 1500 }, ) .catch(() => {}); console.error(`[fetch-orders] Page loaded for order ${order.id}`); // Run extractions in parallel for speed const extractionPromises: Promise<void>[] = []; // Order details extraction extractionPromises.push( extractOrderDetails(page, region) .then((details) => { Object.assign(order, details); console.error(`[fetch-orders] Order details extracted`); }) .catch(() => {}), ); // Items extraction if (includeItems) { extractionPromises.push( plugin .extractItems(page, header) .then((items) => { console.error(`[fetch-orders] Found ${items.length} items`); result.items.push(...items); order.items = items; }) .catch(() => { order.items = []; }), ); } // Shipments extraction if (includeShipments) { extractionPromises.push( plugin .extractShipments(page, header, fetchTrackingNumbers) .then((shipments) => { result.shipments.push(...shipments); order.shipments = shipments; }) .catch(() => {}), ); } // Transactions extraction if (includeTransactions) { extractionPromises.push( plugin .extractTransactions(page, header) .then((transactions) => { result.transactions.push(...transactions); }) .catch(() => {}), ); } // Wait for all extractions to complete await Promise.all(extractionPromises); processedCount++; } } catch (error) { result.errors.push(`Error extracting order ${order.id}: ${error}`); processedCount++; } } } onProgress?.( `Complete! ${result.orders.length} orders, ${result.items.length} items`, result.orders.length, result.orders.length, ); return result; } catch (error) { result.errors.push(`Fetch error: ${error}`); return result; } }
- src/index.ts:132-504 (registration)Tool registration in the MCP tools array used by ListToolsRequestSchema handlerconst tools: Tool[] = [ { name: "get_amazon_orders", description: "Fetch Amazon order history for a specified date range or year. Returns order summaries including: order ID, date, total amount, status, item count, shipping address (7 lines), payment method, and Subscribe & Save frequency. Optionally includes detailed item data (ASIN, name, price, quantity, seller, condition) and shipment tracking. Use for browsing order history or building reports.", inputSchema: { type: "object", properties: { region: { type: "string", description: `Amazon region code. Supported: ${getRegionCodes().join(", ")}`, enum: getRegionCodes(), }, year: { type: "number", description: "Year to fetch orders from (e.g., 2024). If omitted, uses current year.", }, start_date: { type: "string", description: "Start date in ISO format (YYYY-MM-DD). Overrides year if provided.", }, end_date: { type: "string", description: "End date in ISO format (YYYY-MM-DD). Overrides year if provided.", }, include_items: { type: "boolean", description: "Extract item details (ASIN, name, price, quantity, seller, condition) from each order's invoice page. Adds ~2s per order.", default: false, }, include_shipments: { type: "boolean", description: "Extract shipment info (delivery status, tracking link) from each order's detail page. Adds ~2s per order. Note: tracking link URL is captured but not the carrier tracking number - use fetch_tracking_numbers for that.", default: false, }, fetch_tracking_numbers: { type: "boolean", description: "Extract actual carrier tracking numbers (e.g., AZ218181365JE) by visiting each shipment's 'Track package' page. Adds ~2s per shipment. Only works when include_shipments is true.", default: false, }, max_orders: { type: "number", description: "Maximum number of orders to fetch. Use to limit results for large accounts or avoid timeouts.", }, }, required: ["region"], }, }, { name: "get_amazon_order_details", description: "Get comprehensive details for a specific Amazon order by order ID. Returns full order data including: items (ASIN, name, price, quantity, seller, condition), financial breakdown (subtotal, shipping, tax, VAT, promotions, total), shipping address, payment methods, and optionally shipment tracking and transaction history.", inputSchema: { type: "object", properties: { order_id: { type: "string", description: "Amazon order ID in format XXX-XXXXXXX-XXXXXXX (e.g., 123-4567890-1234567)", }, region: { type: "string", description: "Amazon region code where the order was placed", enum: getRegionCodes(), }, include_shipments: { type: "boolean", description: "Extract shipment info from order detail page (default: true)", default: true, }, fetch_tracking_numbers: { type: "boolean", description: "Extract actual carrier tracking number (e.g., AZ218181365JE) by visiting the 'Track package' page. Adds ~2s per shipment.", default: false, }, include_transactions: { type: "boolean", description: "Include payment transaction details (default: false)", default: false, }, }, required: ["order_id", "region"], }, }, { name: "export_amazon_orders_csv", description: "Export Amazon orders summary to CSV file. Fast extraction from order list page (~0.5s per 10 orders). CSV columns: Order ID, Date, Total, Status, Item Count, Address (7 lines), Subscribe & Save, Platform, Region, Order URL. Defaults to ~/Downloads with auto-generated filename. For large accounts (500+ orders), use max_orders to batch exports and avoid timeouts.", inputSchema: { type: "object", properties: { region: { type: "string", description: "Amazon region code", enum: getRegionCodes(), }, year: { type: "number", description: "Year to export (defaults to current year)", }, start_date: { type: "string", description: "Start date in ISO format (YYYY-MM-DD)", }, end_date: { type: "string", description: "End date in ISO format (YYYY-MM-DD)", }, output_path: { type: "string", description: "Full path to save CSV file. Defaults to ~/Downloads/amazon-{region}-orders-{year}-{date}.csv", }, max_orders: { type: "number", description: "Maximum number of orders to export. Recommended: 100-200 per batch for large accounts.", }, }, required: ["region"], }, }, { name: "export_amazon_items_csv", description: "Export detailed Amazon order items to CSV file. Visits each order's invoice page to extract item-level data (~2s/order). CSV columns: Order ID, Date, ASIN, Product Name, Condition, Quantity, Unit Price, Item Total, Seller, Subscribe & Save, Order financials (Subtotal, Shipping, Tax, VAT, Promotion, Total), Status, Address (7 lines), Payment Method, Product URL, Order URL, Region. Ideal for expense tracking, inventory analysis, or accounting exports.", inputSchema: { type: "object", properties: { region: { type: "string", description: "Amazon region code", enum: getRegionCodes(), }, year: { type: "number", description: "Year to export (defaults to current year)", }, start_date: { type: "string", description: "Start date in ISO format (YYYY-MM-DD)", }, end_date: { type: "string", description: "End date in ISO format (YYYY-MM-DD)", }, output_path: { type: "string", description: "Full path to save CSV file. Defaults to ~/Downloads/amazon-{region}-items-{year}-{date}.csv", }, max_orders: { type: "number", description: "Maximum number of orders to process. Recommended: 50-100 per batch due to ~2s/order extraction time.", }, }, required: ["region"], }, }, { name: "export_amazon_shipments_csv", description: "Export Amazon shipment tracking data to CSV file. Visits each order's detail page to extract tracking info (~4s/order). CSV columns: Order ID, Date, Shipment ID, Status, Delivered (Yes/No/Unknown), Tracking ID, Tracking URL, Items in Shipment, Item Names, Payment Amount, Refund. Useful for tracking deliveries and reconciling shipments.", inputSchema: { type: "object", properties: { region: { type: "string", description: "Amazon region code", enum: getRegionCodes(), }, year: { type: "number", description: "Year to export (defaults to current year)", }, start_date: { type: "string", description: "Start date in ISO format (YYYY-MM-DD)", }, end_date: { type: "string", description: "End date in ISO format (YYYY-MM-DD)", }, output_path: { type: "string", description: "Full path to save CSV file. Defaults to ~/Downloads/amazon-{region}-shipments-{year}-{date}.csv", }, max_orders: { type: "number", description: "Maximum number of orders to process. Recommended: 25-50 per batch due to ~4s/order extraction time.", }, fetch_tracking_numbers: { type: "boolean", description: "Extract actual carrier tracking numbers (e.g., AZ218181365JE) by visiting each shipment's 'Track package' page. Adds ~2s per shipment.", default: false, }, }, required: ["region"], }, }, { name: "export_amazon_transactions_csv", description: "Export Amazon payment transactions to CSV file. Extracts transaction data from each order's detail page. CSV columns include: date, order ID, amount, payment method, card info. For faster bulk transaction export, consider get_amazon_transactions which scrapes the dedicated transactions page.", inputSchema: { type: "object", properties: { region: { type: "string", description: "Amazon region code", enum: getRegionCodes(), }, year: { type: "number", description: "Year to export (defaults to current year)", }, start_date: { type: "string", description: "Start date in ISO format (YYYY-MM-DD)", }, end_date: { type: "string", description: "End date in ISO format (YYYY-MM-DD)", }, output_path: { type: "string", description: "Full path to save CSV file. Defaults to ~/Downloads/amazon-{region}-transactions-{year}-{date}.csv", }, max_orders: { type: "number", description: "Maximum number of orders to process", }, }, required: ["region"], }, }, { name: "get_amazon_transactions", description: "Fetch all Amazon payment transactions from the dedicated transactions page. Faster than per-order extraction as it scrapes the infinite-scroll transactions list. Returns: date, order IDs, amount, payment method, card info (last 4 digits), vendor. Useful for reconciling payments, tracking spending, or exporting for accounting.", inputSchema: { type: "object", properties: { region: { type: "string", description: "Amazon region code", enum: getRegionCodes(), }, start_date: { type: "string", description: "Start date filter in ISO format (YYYY-MM-DD)", }, end_date: { type: "string", description: "End date filter in ISO format (YYYY-MM-DD)", }, max_scrolls: { type: "number", description: "Maximum scroll attempts to load more transactions. Default: 50. Increase for longer history.", }, }, required: ["region"], }, }, { name: "get_amazon_gift_card_balance", description: "Get current Amazon gift card balance and transaction history. Returns: current balance, last updated timestamp, and paginated transaction history (date, description, amount, closing balance, type, associated order ID, claim code, serial number). Supports fetching complete history across multiple pages.", inputSchema: { type: "object", properties: { region: { type: "string", description: "Amazon region code", enum: getRegionCodes(), }, max_pages: { type: "number", description: "Maximum pages of transaction history to fetch. Default: 10. Set to 0 for unlimited.", default: 10, }, fetch_all_pages: { type: "boolean", description: "Automatically paginate through all available transaction history. Default: true.", default: true, }, }, required: ["region"], }, }, { name: "export_amazon_gift_cards_csv", description: "Export Amazon gift card transaction history to CSV file. CSV columns: Date, Description, Amount, Closing Balance, Type (credit/debit), Order ID, Claim Code, Serial Number, Region. Useful for tracking gift card usage and reconciling balances.", inputSchema: { type: "object", properties: { region: { type: "string", description: "Amazon region code", enum: getRegionCodes(), }, output_path: { type: "string", description: "Full path to save CSV file. Defaults to ~/Downloads/amazon-{region}-gift-cards-{date}.csv", }, max_pages: { type: "number", description: "Maximum pages of transaction history to fetch. Default: 10. Set to 0 for unlimited.", default: 10, }, }, required: ["region"], }, }, { name: "get_amazon_gift_card_transactions", description: "Get Amazon gift card transaction history with full details. Returns: current balance, transaction count, and detailed transactions (date, description, amount, closing balance, transaction type, order ID, claim code, serial number). Supports pagination for complete history.", inputSchema: { type: "object", properties: { region: { type: "string", description: "Amazon region code", enum: getRegionCodes(), }, max_pages: { type: "number", description: "Maximum pages of transaction history to fetch. Default: 10. Set to 0 for unlimited.", default: 10, }, }, required: ["region"], }, }, { name: "check_amazon_auth_status", description: "Check if the browser session is authenticated with Amazon for a specific region. Returns authentication status (authenticated/not authenticated), current URL, and any error messages. Use this to verify login status before running other tools, or to prompt user to log in if session expired.", inputSchema: { type: "object", properties: { region: { type: "string", description: "Amazon region code to check authentication for", enum: getRegionCodes(), }, }, required: ["region"], }, }, ];
- src/tools/fetch-orders.ts:25-50 (helper)Helper function to parse multi-line shipping addresses from invoice extraction into structured format used in order datafunction parseInvoiceAddressLines(lines: string[]): { line1?: string; line2?: string; line3?: string; line4?: string; line5?: string; line6?: string; line7?: string; } { if (lines.length === 0) return {}; // Clean each line and filter empty ones const cleaned = lines .map((l) => l.replace(/\s+/g, " ").trim()) .filter((l) => l.length > 0); return { line1: cleaned[0], line2: cleaned[1], line3: cleaned[2], line4: cleaned[3], line5: cleaned[4], line6: cleaned[5], line7: cleaned[6], }; }