Skip to main content
Glama
listingbureau

Listing Bureau - Amazon Organic Ranking

lb_estimate_cost

Read-only

Estimate Amazon organic ranking campaign cost before committing. Provide uniform daily volumes or a per-day schedule; tool computes total cost, daily averages, and wallet sustainability.

Instructions

Estimate campaign cost before committing. Fetches current rates and wallet balance, then computes total cost, daily averages, and wallet sustainability. Provide either uniform daily volumes (atc/sfb/pgv + num_days) or a per-day schedule array. Include retail_price for accurate SFB costs. SFB is US-region only; ATC/PGV work in all regions (lower execution rate outside US). Pass region to validate SFB eligibility.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
regionNoAmazon region code — if provided with SFB volumes, validates SFB is allowed (US only). GB accepted as alias for UK.
atcNoUniform daily add-to-cart volume (all regions; lower execution rate outside US)
sfbNoUniform daily Search Find Buy (SFB) volume (US-region projects only)
pgvNoUniform daily page view volume (all regions; lower execution rate outside US)
num_daysNoNumber of days for uniform volumes
scheduleNoPer-day schedule entries (alternative to uniform volumes)
retail_priceNoProduct retail price in USD — needed for accurate SFB cost including product price and fees

Implementation Reference

  • Main handler for lb_estimate_cost tool. Fetches service rates and wallet balance, validates inputs (schedule vs uniform volumes, SFB region), computes cost estimate via estimateCost(), calculates wallet sustainability (days_affordable), and generates SFB lock commitment info.
    server.tool(
      "lb_estimate_cost",
      "Estimate campaign cost before committing. Fetches current rates and wallet balance, then computes total cost, daily averages, and wallet sustainability. Provide either uniform daily volumes (atc/sfb/pgv + num_days) or a per-day schedule array. Include retail_price for accurate SFB costs. SFB is US-region only; ATC/PGV work in all regions (lower execution rate outside US). Pass region to validate SFB eligibility.",
      estimateCostShape,
      { readOnlyHint: true },
      async (params) => {
        try {
          // Validate: either schedule array, or (volumes + num_days)
          const hasSchedule = params.schedule && params.schedule.length > 0;
          const hasVolume = (params.atc ?? 0) > 0 || (params.sfb ?? 0) > 0 || (params.pgv ?? 0) > 0;
          if (!hasSchedule && !(hasVolume && params.num_days != null)) {
            return formatErrorResult(
              new Error("Provide either a 'schedule' array, OR at least one volume (atc/sfb/pgv > 0) with 'num_days'"),
            );
          }
    
          // Normalize region once for all downstream use
          const normalizedRegion = params.region ? normalizeRegion(params.region) : undefined;
    
          // SFB region validation
          const hasSfbInput = params.schedule
            ? params.schedule.some((d) => (d.sfb ?? 0) > 0)
            : (params.sfb ?? 0) > 0;
          if (normalizedRegion) {
            // Hard reject: region provided + non-US + has SFB
            assertSfbAllowed(normalizedRegion, hasSfbInput);
          }
    
          const [ratesRes, walletRes] = await Promise.all([
            client.request<ServiceRates>("GET", "/api/v1/account/service-rates", undefined, undefined, "lb_estimate_cost"),
            client.request<WalletBalance>("GET", "/api/v1/wallet", undefined, undefined, "lb_estimate_cost"),
          ]);
    
          const rates = ratesRes.data;
          const wallet = walletRes.data;
    
          // Build normalized schedule
          let schedule: { date: string; atc: number; sfb: number; pgv: number }[];
          if (params.schedule && params.schedule.length > 0) {
            schedule = params.schedule.map((s) => ({
              date: s.date,
              atc: s.atc ?? 0,
              sfb: s.sfb ?? 0,
              pgv: s.pgv ?? 0,
            }));
          } else {
            const numDays = params.num_days!;
            // Uniform volumes: "uniform" label signals these aren't real calendar dates.
            // Daily breakdown (for <= 14 days) will show identical rows — this is intentional.
            schedule = Array.from({ length: numDays }, () => ({
              date: "uniform",
              atc: params.atc ?? 0,
              sfb: params.sfb ?? 0,
              pgv: params.pgv ?? 0,
            }));
          }
    
          const estimate = estimateCost(schedule, rates, params.retail_price);
    
          // Wallet sustainability (clamp to zero for overdraft, round to match grand_total precision)
          const availableUsd = round2(Math.max(0, wallet.balance_usd - wallet.held_usd));
          // null = unlimited (zero-cost schedule can run indefinitely)
          const daysAffordable =
            estimate.avg_daily_cost > 0
              ? Math.floor(availableUsd / estimate.avg_daily_cost)
              : null;
    
          const warnings: string[] = [];
    
          // Warn if schedule array was used but uniform params were also provided
          if (hasSchedule && (hasVolume || params.num_days != null)) {
            warnings.push(
              "schedule array provided — num_days and uniform volumes (atc/sfb/pgv) were ignored.",
            );
          }
    
          // SFB without region warning
          const hasSfb = schedule.some((d) => d.sfb > 0);
          if (hasSfb && !params.region) {
            warnings.push(
              "SFB volumes provided without region — estimate assumes US pricing. Pass region to validate SFB eligibility.",
            );
          }
    
          // SFB without retail_price warning (0 is effectively the same as omitting)
          if (hasSfb && (params.retail_price == null || params.retail_price === 0)) {
            warnings.push(
              "SFB volumes provided without retail_price — estimate uses service fee only ($" +
                rates.sfb_service_fee.toFixed(2) +
                "/unit). Provide retail_price for full cost including product price and fees.",
            );
          }
    
          // Affordability warning (skip for zero-cost schedules to avoid confusing "$0.00 exceeds" messages)
          if (estimate.totals.grand_total > 0 && availableUsd < estimate.totals.grand_total) {
            const affordMsg = daysAffordable != null
              ? `Can afford ~${daysAffordable} days at current rate.`
              : "";
            warnings.push(
              `Campaign cost ($${estimate.totals.grand_total.toFixed(2)}) exceeds available balance ($${availableUsd.toFixed(2)}). ${affordMsg}`.trim(),
            );
          }
    
          const sfbUnit = sfbUnitCost(rates, params.retail_price);
    
          // Note rounding caveat when daily breakdown is present
          if (estimate.daily_breakdown) {
            warnings.push(
              "daily_breakdown rows are rounded independently — their sum may differ from grand_total by a few cents. Trust grand_total for the accurate figure.",
            );
          }
    
          const result: Record<string, unknown> = {
            estimate,
            wallet: {
              balance_usd: wallet.balance_usd,
              held_usd: wallet.held_usd,
              available_usd: round2(availableUsd),
              // >= is intentional: exact-balance means campaign is affordable
              can_afford_campaign: availableUsd >= estimate.totals.grand_total,
              // null = zero-cost schedule (unlimited days), number = finite affordability
              days_affordable: daysAffordable,
            },
            rates_used: {
              atc_per_action: rates.atc,
              pgv_per_action: rates.pgv,
              sfb_per_unit: round2(sfbUnit),
              sfb_formula: rates.sfb_formula,
              sfb_retail_price_provided: params.retail_price != null,
            },
          };
    
          // SFB lock commitment info
          const lockDays = rates.sfb_lock_days ?? 0;
          if (hasSfb && lockDays > 0) {
            // Compute earliest SFB date from server date
            const serverDate = rates.server_date;
            let earliestSfbDate: string | undefined;
            if (serverDate) {
              const d = new Date(serverDate + "T00:00:00Z");
              d.setUTCDate(d.getUTCDate() + lockDays);
              earliestSfbDate = d.toISOString().split("T")[0];
            }
    
            // Filter locked SFB units based on schedule type
            let totalLockedSfb = 0;
            if (schedule.some((d) => d.date === "uniform")) {
              // Uniform schedule: first lockDays entries fall within the freeze period
              totalLockedSfb = schedule.slice(0, lockDays).reduce((sum, d) => sum + d.sfb, 0);
            } else if (serverDate) {
              // Dated schedule: filter entries whose date falls within the freeze period
              const boundary = new Date(serverDate + "T00:00:00Z");
              boundary.setUTCDate(boundary.getUTCDate() + lockDays);
              const boundaryStr = boundary.toISOString().split("T")[0];
              totalLockedSfb = schedule
                .filter((d) => d.date < boundaryStr)
                .reduce((sum, d) => sum + d.sfb, 0);
            }
    
            if (totalLockedSfb > 0) {
              const lockCost = round2(totalLockedSfb * sfbUnit);
    
              result.sfb_lock = {
                lock_days: lockDays,
                locked_sfb_units: totalLockedSfb,
                lock_commitment_usd: lockCost,
                earliest_sfb_date: earliestSfbDate,
                note: `First ${lockDays} days of SFB (${totalLockedSfb} units, $${lockCost.toFixed(2)}) fall within the freeze period once scheduled and cannot be cancelled or changed.`,
              };
    
              if (lockCost > availableUsd) {
                warnings.push(
                  `SFB freeze period cost ($${lockCost.toFixed(2)}) exceeds available balance ($${availableUsd.toFixed(2)}). Schedule will be rejected.`,
                );
              }
            }
          }
    
          if (warnings.length > 0) {
            result.warnings = warnings;
          }
    
          return formatResult(result);
        } catch (e) {
          return formatErrorResult(e);
        }
      },
    );
  • Input schema for lb_estimate_cost tool, defining region, atc, sfb, pgv, num_days, schedule array, and retail_price parameters with Zod validation.
    const estimateCostShape = {
      region: z
        .enum(ACCEPTED_REGIONS)
        .optional()
        .describe("Amazon region code — if provided with SFB volumes, validates SFB is allowed (US only). GB accepted as alias for UK."),
      atc: z.number().int().min(0).optional().describe("Uniform daily add-to-cart volume (all regions; lower execution rate outside US)"),
      sfb: z.number().int().min(0).optional().describe("Uniform daily Search Find Buy (SFB) volume (US-region projects only)"),
      pgv: z.number().int().min(0).optional().describe("Uniform daily page view volume (all regions; lower execution rate outside US)"),
      num_days: z.number().int().min(1).max(365).optional().describe("Number of days for uniform volumes"),
      schedule: z
        .array(scheduleItemSchema)
        .min(1)
        .max(365)
        .optional()
        .describe("Per-day schedule entries (alternative to uniform volumes)"),
      retail_price: z
        .number()
        .min(0)
        .optional()
        .describe("Product retail price in USD — needed for accurate SFB cost including product price and fees"),
    };
  • src/index.ts:56-62 (registration)
    Registration call in the main server setup — registerCostTools(server, client) is invoked to register lb_estimate_cost on the MCP server.
    registerAccountTools(server, client);
    registerWalletTools(server, client);
    registerProjectsTools(server, client);
    registerScheduleTools(server, client);
    registerOrdersTools(server, client);
    registerFeedbackTools(server, client);
    registerCostTools(server, client);
  • Core cost estimation logic used by the handler. Iterates schedule days, multiplies by rates, and returns totals, daily breakdown, and avg daily cost.
    export function estimateCost(
      schedule: { date: string; atc: number; sfb: number; pgv: number }[],
      rates: ServiceRates,
      retailPrice?: number,
    ): CostEstimate {
      if (schedule.length === 0) {
        return {
          num_days: 0,
          totals: { atc: 0, pgv: 0, sfb: 0, grand_total: 0 },
          avg_daily_cost: 0,
        };
      }
    
      const sfbUnit = sfbUnitCost(rates, retailPrice);
      const includeDailyBreakdown = schedule.length <= 14;
    
      let totalAtc = 0;
      let totalSfb = 0;
      let totalPgv = 0;
      const dailyBreakdown: DailyCost[] = [];
    
      for (const day of schedule) {
        const dayAtc = day.atc * rates.atc;
        const daySfb = day.sfb * sfbUnit;
        const dayPgv = day.pgv * rates.pgv;
        totalAtc += dayAtc;
        totalSfb += daySfb;
        totalPgv += dayPgv;
    
        if (includeDailyBreakdown) {
          dailyBreakdown.push({
            date: day.date,
            atc: round2(dayAtc),
            sfb: round2(daySfb),
            pgv: round2(dayPgv),
            total: round2(dayAtc + daySfb + dayPgv),
          });
        }
      }
    
      const grandTotal = totalAtc + totalSfb + totalPgv;
      const numDays = schedule.length;
    
      return {
        num_days: numDays,
        totals: {
          atc: round2(totalAtc),
          pgv: round2(totalPgv),
          sfb: round2(totalSfb),
          grand_total: round2(grandTotal),
        },
        avg_daily_cost: round2(numDays > 0 ? grandTotal / numDays : 0),
        ...(includeDailyBreakdown ? { daily_breakdown: dailyBreakdown } : {}),
      };
    }
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations declare readOnlyHint=true, and the description confirms it's an estimation (no commitment). Adds value by detailing what is computed (total cost, daily averages, wallet sustainability) and regional behavior differences (SFB US-only, ATC/PGV lower outside US). No contradictions.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Description is compact, front-loaded with purpose, and each sentence adds necessary information. No redundancy or unnecessary details.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (7 params, two modes) and no output schema, the description covers input usage well but lacks details about the output format (e.g., structure of computed costs and sustainability). Generally sufficient for an estimation tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema has 100% coverage with descriptions for each parameter. Description adds contextual meaning beyond schema (e.g., two-volume modes, dual use of retail_price for SFB accuracy). Provides clarity not found in schema alone.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Description clearly states the tool estimates campaign cost before committing. It specifies what it does (fetches rates and balance, computes costs and sustainability) and is unique among siblings (no other cost estimation tool).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly describes two usage modes (uniform daily volumes or per-day schedule) and conditions for SFB (US only) and retail_price requirement. Provides clear guidance on when to use region parameter.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/listingbureau/listingbureau-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server