Documentation

Two CSVs in. Cash decisions out.

stockcash is an MCP server for small Amazon FBA and Shopify / DTC sellers (roughly $10k–$250k/month, 100–1,500 SKUs) and for agencies running several stores. It reads your own inventory and order exports, runs deterministic arithmetic, and returns markdown or JSON. In local mode it makes zero network calls while analysing and stores its memory on your machine.

New here? Ask your agent for demo_report first. It needs no files and prints a full 32-SKU report so you can judge the output.

Quickstart

  1. Install and build (Node 20+).
  2. Add the server to your MCP client.
  3. Call validate_exports with your two files.
  4. Call analyze_inventory, then decide what to mark down and reorder.
git clone <your-repo> stockcash-mcp
cd stockcash-mcp
npm install
npm run build

# no files needed:
npm run demo

Install

Hosted — one URL

Sign in, copy your API key, and add one endpoint. No build, no clone.

claude mcp add --transport http stockcash https://mcp.yourdomain.com/mcp \
  --header "Authorization: Bearer sc_live_..."

JSON clients use the same URL with a header. Get the key from your dashboard. Hosted calls are metered against your account.

Claude web/mobile: add the same URL and approve the OAuth prompt after signing in. CLI clients can keep using the API key header.

Hosted tools cannot read paths on your computer. Send the CSV content as inventoryCsv / ordersCsv text; store profiles keep thresholds and snapshot history, not file paths.

Local — stdio

Claude Desktop / Cursor / most clients

{
  "mcpServers": {
    "stockcash": {
      "command": "node",
      "args": ["/absolute/path/to/stockcash-mcp/dist/index.js"],
      "env": { "STOCKCASH_LICENSE": "sc_..." }
    }
  }
}

OpenCode

{
  "mcp": {
    "servers": {
      "stockcash": {
        "type": "local",
        "command": ["node", "/absolute/path/to/stockcash-mcp/dist/index.js"]
      }
    }
  }
}

HTTP mode

npm run build
node dist/index.js --http --port 8787
# POST http://127.0.0.1:8787/mcp   (stateless Streamable HTTP)
# GET  http://127.0.0.1:8787/health

The local build needs no key on the Free plan. Paste a license key only when you want snapshots, digest or more credits. The hosted URL is separate and always authenticated with an API key.

Tools & credits

ToolCreditsFreePro
demo_report0✓✓
validate_exports0✓✓
store_profile01 store∞
license0✓✓
cash_summary1✓✓
trend_report1✓✓
snapshot1—✓
analyze_inventory2✓✓
dead_stock_report2✓✓
reorder_plan2✓✓
bundle_ideas2✓✓
weekly_digest3—✓
portfolio_digest5—✓

Only successful calls are charged. A failed call returns an error and costs nothing.

The recommended weekly loop

store_profile {action:"save", name:"cedar-clay", inventoryPath:"...", ordersPath:"..."}
snapshot      {store:"cedar-clay"}      # once, baseline
weekly_digest {store:"cedar-clay"}      # every Monday

CSV format

Only four inventory columns and three order columns are strictly required. Header aliases are matched case- and punctuation-insensitively.

Inventory

FieldRequiredAccepted headers
SKUyessku, seller_sku, MSKU, item_sku, ASIN, fnsku
On handyesquantity, qty, on_hand, available, AFN Fulfillable Quantity
Inbound / on orderoptionalinbound, in_transit, on_order, open_po, AFN Inbound
Unit coststrongunit_cost, cost, landed_cost, cogs, average_cost
Pricestrongprice, sale_price, list_price, selling_price
Title / category / lead timeoptionaltitle, product_name, category, lead_time_days

Orders

FieldRequiredAccepted headers
Order dateyesdate, order_date, purchase_date, shipped_date
SKUyessame aliases as inventory
Unitsyesquantity, qty, units, units_sold, ordered_units
Revenueoptionalrevenue, sales, item_total, order_total
Order idoptionalorder_id, amazon_order_id, order_number
Negative quantities are treated as returns and netted against sales. Exact duplicate rows (same order id, SKU, date, units and revenue) are counted once. If revenue is missing it is estimated as units × price, and the report says so.

How the math works

MetricFormula
Velocityunits sold in window ÷ windowDays
Days of coverunits on hand ÷ velocity (never if no sales)
Cash tiedunits on hand × unit cost — money already spent, not a loss
Statusout · low ≤30d · healthy 30–120d · slow 120–180d · overstock still selling >180d · dead zero sales
Break-even discount1 − (unit cost ÷ price)
Reorder quantityvelocity × (lead time + targetCoverDays) − on hand − on order
Bundle capacitymin(dead units on hand, partner units sold in window)
Trendwindow split in half: declining ≤−30% · rising ≥+30% · stopped · long-dead · new · stable

“Today” is the newest order date in the export, so old files still produce sane reports. Pass referenceDate to pin it. A single typo date years in the future cannot drag the window forward when the file straddles the machine clock.

What it deliberately does not do

  • No price-elasticity model: it shows the lift you need, not a prediction of units sold.
  • No fees, shipping or storage beyond unit cost. Put them in your cost column for a landed-cost view.
  • No forecasts from other sellers’ data. Every number comes from your rows.

Data safety rails

  • Zero SKU matches prints a warning as the first insight instead of silently reporting everything as dead.
  • Impossible dates (2026-02-31, month 13, hour 25) are rejected rather than rolled into the next month.
  • Semicolon or tab exports get an explicit hint, not just “column not found”.
  • European numbers (1.234,56, 12,5) parse correctly alongside US ones.
  • SKUs differing only by case are merged, with a note.
  • Invalid thresholds are clamped into a consistent order so statuses cannot invert.
  • Cash budget: pass availableCash; rows that do not fit are marked deferred, never hidden.

License activation

A license is a signed key, not an account. The server carries a public key and verifies the signature locally. No billing API is contacted at runtime.

Or set it once in the MCP server environment:

// claude_desktop_config.json / opencode.json
"env": { "STOCKCASH_LICENSE": "sc_..." }

What the key contains

  • plan (pro)
  • credit allowance for the period
  • period start and hard expiry
  • a customer/subscription id so credit ledgers never mix
Keys are valid until the printed period end (about 31 days monthly, 365 days annual). Renewal issues a fresh key by email. If a subscription is cancelled, the key simply expires — nothing is deleted from your machine.

Commands

# see plan, credits, limits
license {}

# activate
license {"action":"activate","key":"sc_..."}

# go back to free on this machine
license {"action":"deactivate"}

Store the key in ~/.stockcash-mcp/license.json automatically when you activate through the tool, or in the environment when you prefer config-as-code.

Credits & limits

FreePro
Credits / period201,000
SKUs50∞
Store profiles1∞
Snapshot / digest—✓
Portfolio—✓

The ledger lives in ~/.stockcash-mcp/credits.json and is per license id. A new period or a newly activated key starts a fresh ledger. Top-ups raise the allowance for the current period only.

Local mode enforces credits on your machine, which is the honest trade-off for a no-network tool: the key is signed, so the allowance cannot be edited without invalidating the signature. Hosted mode meters credits server-side in Supabase. Stripe handles the payment; the key is issued by our webhook.

Privacy

  • Inputs are files you download from your own store. Nothing is scraped.
  • The server makes zero network calls — no API keys to leak.
  • Profiles, snapshots and credit ledgers are plain JSON under ~/.stockcash-mcp/. Read, back up or delete them yourself.
  • No third-party accounts, no OAuth, no ToS surface, no regulated data.

Troubleshooting

“Required column sku not found”

Call validate_exports. It prints every header detected, which canonical column each maps to, and the first parsed rows. If the header row looks semicolon- or tab-separated you will get a specific hint.

Everything shows as dead

Usually the two files do not share an identifier (inventory uses SKU, orders use ASIN). The first insight warns when zero order rows matched. Check ordersWithoutInventory in the same report.

“Not enough credits”

Run license {} to see the balance and period end. Top up or wait for renewal. Failed calls are never charged.

“The Free plan does not include snapshot”

That is the paid boundary: state. Static analysis is free; remembering last week needs Pro. The error names the required feature.

A number looks wrong

Open the full report: it prints every formula and every assumption, plus parsing notes with row numbers. Cash tied is on-hand × unit cost; if your cost column includes fees, the number includes them.