inFlow Inventory MCP Server
# inFlow Inventory MCP Server
A Model Context Protocol (MCP) server that provides comprehensive tools for interacting with the [inFlow Inventory](https://www.inflowinventory.com/) API. This enables AI assistants like Claude to manage your inventory, orders, customers, and more.
Current package version: `1.4.0-alpha.2`
## Features
- **Products**: List, search, create, update products and check inventory levels
- **Sales Orders**: Create customer orders and patch existing orders without dropping unmentioned lines or serial numbers
- **Purchase Orders**: Create vendor purchase orders, receive stock, and reverse received stock
- **Customers & Vendors**: Manage customer and vendor records
- **Inventory Operations**: Stock adjustments, transfers, counts, and manufacturing orders
- **Manufacturing Orders**: Create or patch work orders while preserving output/input lines and serial numbers
- **Product Manufacturing**: Read, compare, preview, and concurrency-check BOM, operation-template, and manufacturing-setting changes
- **Product Groups**: Discover groups, options, variants, and per-location variant quantities
- **Planning & Audit**: Product-group manufacturing audit and exact direct/leaf/net BOM requirements
- **Prices & Traceability**: Exact pricing-scheme rows plus manufacturing-order line/pick/matching trace
- **Mutation Safety**: Signed previews, dual current-state hashes, timestamps, durable status, idempotency, and readback verification
- **Serial Numbers**: Query serial numbers from orders or product inventory lines
- **Reference Data**: Locations, categories, pricing schemes, payment terms, currencies, tax codes
- **Webhooks**: Subscribe to inFlow events
## Prerequisites
- Node.js 22
- An active inFlow Inventory subscription with API add-on
- inFlow API credentials (Company ID and API Key)
## Installation
```bash
git clone https://github.com/bigl34/inflow-mcp-server.git
cd inflow-mcp-server
# Install dependencies
npm install
# Build the TypeScript
npm run build
```
## Configuration
### Getting Your API Credentials
1. Log in to your inFlow Inventory account
2. Go to **Settings** > **Integrations**
3. Find your **Company ID** on the integrations page
4. Click **Add New API Key** to generate a new key
### Environment Variables
Set the following environment variables:
```bash
# Required
export INFLOW_COMPANY_ID="your-company-id"
export INFLOW_API_KEY="your-api-key"
# Optional
export INFLOW_BASE_URL="https://cloudapi.inflowinventory.com" # Default
export INFLOW_API_VERSION="2026-04-13" # Default API version
# Safe preview/apply control plane (closed by default)
export INFLOW_ENABLE_SAFE_WRITES="false"
export INFLOW_ENABLE_STOCK_WRITES="false"
# Manufacturing pick-batch additionally requires this gate
export INFLOW_ENABLE_MANUFACTURING_PICK_BATCH_WRITES="false"
# Deprecated diagnostic compatibility inputs; these authorize no safe writes
export INFLOW_ENABLE_MANUFACTURING_WRITES="false"
export INFLOW_ENABLE_PRICE_WRITES="false"
export INFLOW_ENABLE_PRODUCT_GROUP_WRITES="false"
export INFLOW_ENABLE_MO_SERIAL_WRITES="false"
export INFLOW_ENABLE_STANDARD_WRITES="false"
export INFLOW_STATE_DIR="$HOME/.local/state/inflow-mcp"
export INFLOW_RATE_LIMIT="60" # Requests per minute (default: 60)
export INFLOW_REQUEST_TIMEOUT="30000" # Request timeout in ms (default: 30000)
export INFLOW_MAX_RETRIES="3" # Max retries on 5xx/429 errors (default: 3)
export INFLOW_RETRY_DELAY="1000" # Initial retry delay in ms (default: 1000)
export INFLOW_DEBUG="true" # Enable debug logging (default: false)
```
### Manufacturing-run coordinator
The manufacturing-run coordinator is a separate HTTP/worker binary; it does
not run inside the stdio MCP server. Build first, then use
`npm run validate:coordinator` to validate the secure credential environment
without starting the listener, or `npm run start:coordinator` to start the
loopback service. The launcher reads its absolute `0600`, single-link,
current-user-owned credential file from the Mac runtime configuration.
Build identity is derived from the running release artifacts; the coordinator
credential file must not contain `INFLOW_ADAPTER_MANIFEST_HASH` or
`INFLOW_PROBE_BUILD` assertions.
In addition to the normal inFlow company/API values, the coordinator
credential environment requires:
```bash
INFLOW_COORDINATOR_HMAC_CURRENT_KID=coordinator-current
INFLOW_COORDINATOR_HMAC_CURRENT_SECRET=<at-least-32-bytes>
INFLOW_COORDINATOR_RUN_READY_WEBHOOK_URL=https://hooks.zapier.com/...
INFLOW_COORDINATOR_TERMINAL_WEBHOOK_URL=https://hooks.zapier.com/...
```
Optional rotation and delivery controls are:
```bash
INFLOW_COORDINATOR_HMAC_NEXT_KID=coordinator-next
INFLOW_COORDINATOR_HMAC_NEXT_SECRET=<at-least-32-bytes>
INFLOW_COORDINATOR_WEBHOOK_AUDIENCE=zapier-webhook
INFLOW_COORDINATOR_WEBHOOK_MAX_ATTEMPTS=5
INFLOW_COORDINATOR_WEBHOOK_RETRY_DELAY_MS=5000
INFLOW_COORDINATOR_WEBHOOK_TIMEOUT_MS=10000
INFLOW_COORDINATOR_WEBHOOK_CLAIM_TTL_MS=30000
```
Both callback URLs must be HTTPS and must not contain credentials. Callback
events are stored transactionally with their state transition and delivered
at least once with deterministic event IDs. The delivery timeout must be
shorter than the durable claim TTL. Each request body contains a canonical
`manufacturing-run-callback/v1` envelope whose HMAC binds the callback kind,
event and operation identity, state revision, marker, audience, company,
timestamp, nonce, key ID, and payload hash. A callback workflow must verify
that body signature before any status, resolve, claim, or Slack action; HTTP
headers alone are not callback authorization. Terminal claims are bound to the
deterministic notification ID inside the verified payload, so an out-of-order
or replayed callback cannot claim another pending notification. Slack
notification posts use a separate claim/ack outbox: an ambiguous post must be marked
`delivery_unknown` and reconciled explicitly, never reposted automatically.
The coordinator's stock-moving manufacturing writes are controlled by their
environment gates alone. That coordinator boundary is separate from product
BOM/config confirmation.
New manufacturing runs use `manufacturing-run-identity/v2`, keeping the
source serial and finished serial as separate immutable identities. Before
component collection, the coordinator validates the complete rooted expanded
MO hierarchy, excludes expanded structural subassemblies from consumption,
and binds the source serial to exactly one quantity-one consumable raw line at
the selected location. The binding is revalidated at readiness and immediately
before the dispatch fence. Exact bound serials may proceed when aggregate
availability covers the requested unit even if unrelated inventory is
allocated (for example, 27 on hand and 24 available); an unavailable binding
blocks with structured evidence and never substitutes another serial.
New v1 begins are refused. Stored v1 runs remain readable: an already-fenced
`dispatch_uncertain` run may perform exact readback-only reconciliation, while
other active v1 states are quarantined as blocked and cannot prepare or
dispatch a provider mutation.
### Claude Desktop Configuration
Add to your Claude Desktop configuration file:
**macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
**Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
```json
{
"mcpServers": {
"inflow-inventory": {
"command": "node",
"args": ["/path/to/inflow-mcp-server/dist/index.js"],
"env": {
"INFLOW_COMPANY_ID": "your-company-id",
"INFLOW_API_KEY": "your-api-key"
}
}
}
}
```
## Available Tools
### Product Management
| Tool | Description |
|------|-------------|
| `list_products` | Search and filter products |
| `get_product` | Get product details by ID (use `include=itemBoms` for BOM) |
| `set_product` | Preview/apply a bounded product create/update; patch-mode `customFields` merges supplied keys while preserving siblings (every `customFields`-bearing `set_*` adapter shares this rule) |
| `upsert_product` | **Deprecated immediate write** retained only for the 1.4 compatibility window |
| `get_inventory_summary` | Get stock levels across locations |
| `get_inventory_summaries_batch` | Batch get stock levels (max 100) |
| `get_bill_of_materials` | Get enriched BOM/manufacturing state for any product |
| `compare_product_boms` | Compare components, operation templates, and settings for 2-25 products |
| `set_product_manufacturing_config` | Preview or apply an exact-scope explicitly confirmed manufacturing configuration change |
| `list_product_groups` | List product groups with options and variants |
| `get_product_group` | Get one product group with options and variants |
| `get_product_group_variant_quantities` | Get variant quantities for a group at one location |
| `list_operation_types` / `get_operation_type` | Discover manufacturing operation types |
| `get_product_prices` / `set_product_prices` | Read or preview/apply exact pricing-scheme rows |
| `copy_product_manufacturing_config` | Explicitly confirmed copy of selected components, operations, or settings without source row IDs |
| `audit_product_group_manufacturing` | Audit option combinations and manufacturing consistency |
| `calculate_bom_requirements` | Calculate exact direct, leaf, or net material requirements |
| `set_product_group_config` | Preview/apply exact-ID group option/value/variant changes with preservation checks |
| `create_product_group_variants` | Preview/apply deterministic stocked-product creation and group attachment |
### Sales Orders
| Tool | Description |
|------|-------------|
| `list_sales_orders` | Search and filter sales orders |
| `get_sales_order` | Get order details by ID |
| `upsert_sales_order` | Create an order or partially update an existing order. Updates preserve unmentioned header fields and lines, merge item patches by line ID or unambiguous product ID, and remove lines listed in `deleteLineIds`. |
### Purchase Orders
| Tool | Description |
|------|-------------|
| `list_purchase_orders` | Search and filter purchase orders |
| `get_purchase_order` | Get order details by ID |
| `upsert_purchase_order` | **Deprecated immediate write** retained only for the 1.4 compatibility window |
| `receive_purchase_order` | **Deprecated immediate stock write** retained only for the 1.4 compatibility window |
| `unreceive_purchase_order` | **Deprecated immediate stock write** retained only for the 1.4 compatibility window |
### Customers
| Tool | Description |
|------|-------------|
| `list_customers` | Search and filter customers |
| `get_customer` | Get customer details by ID |
| `upsert_customer` | **Deprecated immediate write** retained only for the 1.4 compatibility window |
### Vendors
| Tool | Description |
|------|-------------|
| `list_vendors` | Search and filter vendors |
| `get_vendor` | Get vendor details by ID |
| `upsert_vendor` | **Deprecated immediate write** retained only for the 1.4 compatibility window |
### Inventory Operations
| Tool | Description |
|------|-------------|
| `list_stock_adjustments` | List stock adjustments |
| `get_stock_adjustment` | Get adjustment details |
| `upsert_stock_adjustment` | Create/update stock adjustment |
| `list_stock_transfers` | List stock transfers |
| `get_stock_transfer` | Get transfer details |
| `upsert_stock_transfer` | Create/update stock transfer |
| `list_stock_counts` | List inventory counts |
| `get_stock_count` | Get count details |
| `upsert_stock_count` | Create/update stock count |
| `list_manufacturing_orders` | List work orders |
| `get_manufacturing_order` | Get work order details |
| `upsert_manufacturing_order` | Create a work order or partially update an existing one. Updates preserve unmentioned fields, patch output quantity/serials in place, merge input-line patches, and remove lines listed in `deleteInputLineIds`. |
| `get_manufacturing_order_trace` | Join output lines, pick lines, pick matchings, and serial anomalies |
| `reconcile_manufacturing_order_serials` | Preview exact-ID linked serial changes; apply requires the safe and stock write gates |
### Status and safe writes
`get_mcp_status` returns additive `mcp-status/v2` data without exposing the
company ID or credentials. `writePolicies.safe.operations` reports each
operation's fixed `ordinary` or `stock` classification, static adapter support,
effective apply state, required gates, and reason. Every registered `set_*`,
receipt, reconcile, and `remove_webhook` adapter is statically supported; apply
is controlled only by `INFLOW_ENABLE_SAFE_WRITES` (all) and
`INFLOW_ENABLE_STOCK_WRITES` (stock-affecting). Adapter projections follow the
provider swagger: order and adjustment/transfer rows are `lines[]` with
quantity objects, customers and vendors have no nested contacts include, and
webhooks carry no rowversion. Old per-domain status fields
remain present but are marked deprecated and disabled. `legacyBypassActive` is
always true while deprecated immediate-write tools are registered.
The safe control plane has only two general switches:
- every supported preview/apply dispatch requires
`INFLOW_ENABLE_SAFE_WRITES=true`;
- stock-affecting dispatch also requires
`INFLOW_ENABLE_STOCK_WRITES=true`.
Manufacturing pick-batch additionally requires its dedicated environment gate.
Canary attestations are release evidence only and are not checked at runtime
for the coordinator domains. Product
BOM/manufacturing configuration retains its explicit-confirmation policy and
also requires the master safe-write gate. Every applicable gate is checked
again immediately before network dispatch, so closing a gate invalidates an
existing preview. Closing a gate cannot reverse a completed write; unknown
outcomes remain journalled and are reconciled by readback without redispatch.
`get_mutation_status` reads that durable journal and never repeats a write.
The `1.4.x` compatibility line retains the old immediate `upsert_*`, receipt,
and webhook-delete tools as deprecated interfaces. Their descriptions and
every invocation emit prominent high-severity bypass telemetry, but they remain
callable and are not controlled by `INFLOW_ENABLE_SAFE_WRITES`. New automation
must never fall back to one after a safe rejection and should use
the stable preview-first names: `set_product`, `set_sales_order`,
`set_purchase_order`, `set_purchase_order_receipts`, `set_customer`,
`set_vendor`, `set_stock_adjustment`, `set_stock_transfer`, `set_stock_count`,
`set_manufacturing_order`, `set_taxing_scheme`, `set_webhook`, and
`remove_webhook`. Generic product writes reject fields owned by price,
product-group, or BOM/manufacturing tools. Unknown and mixed operations fail
closed. Static support remains per concrete adapter, so opening a gate cannot
expose an unfinished adapter.
`set_product` is a released ordinary adapter. On every adapter whose writable
projection includes `customFields` (`set_product`, `set_sales_order`,
`set_purchase_order`, `set_customer`, `set_vendor`, `set_stock_adjustment`,
`set_stock_transfer`, `set_manufacturing_order`), patch mode deep-merges the
supplied `customFields` keys into the complete current custom-field object,
including explicit falsy and `null` values; omitted sibling keys are preserved
and sent in the PUT body, because inFlow replaces the whole object on write.
Patch mode does not delete keys, and a non-object patch `customFields`
container fails closed as `INVALID_CUSTOM_FIELDS` on every such adapter.
Replace mode retains whole-object semantics; only `set_product` validates the
replace-mode container. Before the `<resource>/safe-v4` bump only
`set_product` merged; a partial patch on any other adapter replaced the object
and blanked its siblings, and readback could not catch it because desired and
actual dropped the same keys.
The `set_product` writable projection is `name`, `description`, `sku`, `categoryId`,
`isActive`, `weight`, and `customFields` — the scalar properties the Product
schema actually stores. `barcode`, `cost`, `reorderPoint`, `reorderQuantity`,
and `weightUnit` are rejected up front with `OPERATION_UNSUPPORTED` instead of
being sent: inFlow keeps barcodes in `productBarcodes[]` rows and reorder
thresholds in per-location `reorderSettings[]` rows, `cost` is the read-only
`ProductCost` relation derived from purchase receipts and vendor prices, and
the weight unit is a company-wide setting. A PUT carrying any of them returns
200 and silently drops the key, so the readback could never verify. `weight`
is a `decimal` the provider echoes as four-decimal text (`2.5` → `"2.5000"`),
so the semantic projection used for diffs, staleness hashes, update readback
and reconciliation canonicalises only the fields a definition declares in
`decimalFields`; `sku` and every other text field in that projection stay
verbatim, so a leading-zero edit is still a real write. (The create path's
requested-field comparison keeps its earlier decimal-tolerant matching on
every requested leaf.) This moved the product adapter version to
`product/safe-v5`; retained idempotency keys and preview tokens do not cross
that bump. The same echo applies to every other decimal the provider stores:
`exchangeRate` (ten decimals), customer and vendor `discount` (two),
`lines[].unitPrice` / `subTotal` / tax rates (five), and every
`quantity.standardQuantity` / `uomQuantity` on order, adjustment, transfer and
manufacturing rows (four). Each definition declares those `decimalPaths`
(`[]` walks a row array) and the projection canonicalises exactly those
leaves, so `set_sales_order`, `set_purchase_order`, `set_customer`,
`set_vendor`, `set_stock_adjustment`, `set_stock_transfer` and
`set_manufacturing_order` moved to `<resource>/safe-v5`; `set_stock_count`,
`set_taxing_scheme` and `set_webhook` carry no decimals and stay on
`safe-v2`. Row writes in patch or replace mode still compare the caller's rows
against the provider's complete rows, so a row that omits provider-populated
fields (`subTotal`, `description`, tax codes) verifies only through the
create path's requested-field comparison. `get_mutation_status reconcile=true` still settles
`product/safe-v2`..`safe-v4` journal records: it hashes the readback with the
twelve-field projection those versions recorded, so the reconcilable subset
described above is unchanged, while `safe-v5` records use the seven-field
projection.
inFlow stores checkbox custom fields as the text `True`/`False` and date
custom fields as `MM/DD/YYYY 00:00:00`, whatever JSON type the write supplied.
Every generic adapter with a `customFields` projection therefore reads the
tenant's `/custom-field-definitions` (cached per process for five minutes;
`CUSTOM_FIELD_DEFINITIONS_UNAVAILABLE` fails the call closed) and canonicalises
only the slots whose definition type is `checkbox` or `date` inside the
semantic projection used for diffs, staleness hashes, readback verification,
and `get_mutation_status` reconciliation: on a checkbox slot `true`/`"True"`
compare equal, and on a date slot `2026-09-18`, `2026-09-18T00:00:00.000Z`,
and `09/18/2026 00:00:00` compare equal. Text slots are compared verbatim, so a
case-only text edit is still a real write. The dispatched body is not
rewritten. Values a checkbox or date slot holds outside those exact forms
(`Yes`, `-`, non-midnight timestamps, impossible dates) are compared verbatim,
so a write that inFlow reshaped in any other way still reports
`VERIFICATION_MISMATCH`. This changed the product adapter version to
`product/safe-v4` and the other `customFields`-bearing adapters to
`<resource>/safe-v3`; the sibling-preserving patch merge then moved those
other adapters to `<resource>/safe-v4`. Retained idempotency keys and preview
tokens do not cross either bump (a retained key is refused as
`IDEMPOTENCY_KEY_CONFLICT`). Product journal records written under earlier
versions keep the `desiredHash` computed from their raw values, so
reconciliation advances them only when that raw projection already equals the
canonical readback (boolean-only patches on a product whose date slots are
empty); records that carried a date value stay `applied_unverified` and need a
fresh preview/apply. `get_mutation_status` reconciliation is registered only
for `product`, `product-prices`, and `product-manufacturing-config`; a
non-product record left under `safe-v2` or `safe-v3` returns
`ADAPTER_RECONCILIATION_NOT_REGISTERED`, so it cannot be advanced and needs an
owner check of the journal `before` state plus a fresh preview/apply.
Product creation verifies the planned product ID and explicitly supplied
fields, allowing inFlow to populate omitted fields with defaults. The signed
preview, idempotency mapping, and operation journal bind the canonical create
input, including the distinction between omission and explicit `null`.
Existing-product updates retain full desired-state verification. Recovery of a
failed create requires the original request and retained idempotency key;
readback can then verify the same operation without dispatching another write.
Legacy records missing the input binding require a reviewed recovery from the
recorded original request before that key can be reused:
`MutationJournal.restoreInputScope(operationId, inputHash)` binds the hash of
that request to the record and its idempotency mapping, only for reconcilable
keyed creates that carry no binding yet, after the caller has confirmed the
request reproduces the journaled `desiredHash`. The CLI runs the server from
`dist/`, so a source fix is not live until `npm run build` has been rerun.
Idempotency keys are required for creates, additive changes, stock-affecting
changes, and multi-step mutations. Deterministic full replacement with
conclusive readback does not require a key. Deletes bind the exact target in the
preview, verify absence, and return `already_absent` when repeated against a
missing target.
### Serial Numbers
| Tool | Description |
|------|-------------|
| `get_sales_order_serials` | Extract serial numbers assigned to sales order lines |
| `get_purchase_order_serials` | Extract serial numbers assigned to purchase order lines |
| `search_serial_number` | Search fulfilled sales orders for a serial number |
| `list_serial_numbers` | Aggregate serial numbers from fulfilled sales orders |
| `get_product_serials` | Get all serial numbers for a serialized product using product inventory lines |
| `list_all_serials` | List serial numbers across products that track serials |
### Reference Data
| Tool | Description |
|------|-------------|
| `list_locations` | List warehouse locations |
| `get_location` | Get location details |
| `get_suggested_sublocations` | Get bin/shelf suggestions |
| `list_categories` | List product categories |
| `list_pricing_schemes` | List pricing tiers |
| `list_payment_terms` | List payment terms |
| `list_taxing_schemes` | List tax schemes |
| `upsert_taxing_scheme` | Create/update tax scheme |
| `list_tax_codes` | List tax codes |
| `list_currencies` | List currencies |
| `list_adjustment_reasons` | List adjustment reasons |
| `list_custom_field_definitions` | List custom fields |
| `get_custom_field_dropdown_options` | Get dropdown options |
| `list_team_members` | List inFlow users |
### Webhooks
| Tool | Description |
|------|-------------|
| `list_webhooks` | List webhook subscriptions |
| `upsert_webhook` | Create/update webhook |
| `delete_webhook` | Delete webhook |
## Usage Examples
### List Products
```
List all active products in the "Electronics" category
```
### Create a Sales Order
```
Create a sales order for customer "Acme Corp" with:
- 5 units of product SKU-001 at $29.99 each
- 10 units of product SKU-002 at $15.00 each
Required by next Friday
```
### Patch a Sales Order
```
Update sales order SO-1001:
- change the required date to next Monday
- update line abc123 to quantity 2 with serial numbers SN-001 and SN-002
- remove line def456
```
### Receive a Purchase Order
```
Receive all remaining items on purchase order PO-1001 at the Main Warehouse
```
### Reverse a Purchase Order Receipt
```
Unreceive 1 unit of product SKU-001 from purchase order PO-1001 as a dry run first
```
### Check Inventory
```
What's the current stock level for product "Widget Pro" across all locations?
```
### Compare and update a BOM safely
Call `compare_product_boms` with the target product and analogous variants, then
preview the desired change:
```json
{
"productId": "target-product-id",
"mode": "replace",
"components": [
{ "childProductId": "component-id", "quantity": "1" }
],
"dryRun": true
}
```
Apply only by repeating the request with `dryRun:false`, the signed
`previewToken`, `currentSemanticHash`, `currentWriteShapeHash`,
`entityTimestamp`, `desiredHash`, any returned `idempotencyKey`, and
`confirmation:{scope,confirmationHash}` copied from the reviewed fresh preview.
The confirmation hash must be the full lowercase 64-character value. The
server reconstructs the scope from live state and runtime identity and trusts
neither supplied field. It validates confirmation before no-op, idempotency
creation, journalling, or dispatch; a stale build, tenant, source, target, or
state requires a new preview and confirmation.
The CLI flow is:
```bash
bash "$HOME/biz/scripts/cli-run.sh" inflow-inventory-manager \
set-bom --id PRODUCT_ID --mode replace --components '[]'
bash "$HOME/biz/scripts/cli-run.sh" inflow-inventory-manager \
set-bom --id PRODUCT_ID --mode replace --components '[]' \
--apply true --confirm \
--confirm-preview-hash FULL_64_CHARACTER_HASH
```
`INFLOW_ENABLE_MANUFACTURING_WRITES` and
`attestations/manufacturing.json` are deprecated compatibility inputs and do
not authorize or block `set_product_manufacturing_config` or
`copy_product_manufacturing_config`. Those tools retain exact preview
confirmation and now require `INFLOW_ENABLE_SAFE_WRITES=true` at apply. The
manufacturing probe remains useful as non-authorizing API/serializer and
optimistic-concurrency characterization. Stock-moving manufacturing-order
writes require both general gates; pick-batch also requires its dedicated
coordinator gate.
### Manufacturing pick-batch live canary v2
`npm run probe:manufacturing-canary -- --mode pick-batch --scenario <absolute-path> --approvals <absolute-path> --runtime-material <absolute-path>`
runs the checkpointed v2 protocol through the credentialed launcher. The
launcher reads the existing owner-only coordinator credential file, requires
all JSON materials to be absolute owner-only regular files, and refuses to run
unless the master, stock, and coordinator production gates are closed. It never prints
credentials or material bodies. The lower-level
`npm run probe:manufacturing-pick-batch` entry point remains useful for offline
tests but is not the live operator boundary.
The older `manufacturing-pick-batch-canary-scenario/v1` manifest is refused as
not live-executable. Keep
`INFLOW_ENABLE_MANUFACTURING_PICK_BATCH_WRITES` absent/false throughout the
canary and use a dedicated idle `INFLOW_RATE_LIMIT=20` process. The command
uses:
two-subject manifest (`complete` has no operations; `staging` has operations);
stage approvals printed from the current checkpoint/plan;
component intents, canary-only component serials, sentinel exclusion
evidence, and exact expected rejection status/code pairs. V2 accepts only a
predeclared HTTP status in `400`, `409`, `412`, or `422` plus a non-empty
provider code; the observed status and code must both match exactly. The
characterized unavailable-serial response is HTTP `400` with provider code
`NegativeSerialNumberInventory`.
The command is intentionally multi-run. With no approval it creates an
owner-only checkpoint and prints the exact `create` plan. After each approved
stage it stops at the next approval boundary: `create`, `stock-move`,
`negative-probes`, read-only `manual-completion-read`, generated `cleanup`, and
`attest`. Every mutation is durably fenced before dispatch, is dispatched at
most once, and resumes with readback only. Full manufacturing-order reads use
the provider-supported lines/operations/picks/matchings/puts include set;
operation timesheets are normalized when present but are not a live include
relationship. Cleanup is
generated from the latest full bodies and rowversions; attestation is issued
only after exact canonical inventory/serial restoration and real residual
discovery.
The final `attest` rerun performs fresh full-MO, order-number/marker list, and
product inventory-line reads after approval; cached cleanup evidence cannot
mint an attestation if any provider state has drifted.
Live prerequisites are five already isolated, operator-approved canary
products: two finished products, one nonserialized structural subassembly, one
serialized leaf component with dedicated serials, and one nonserialized leaf
component. Each finished product has only the structural subassembly in its
BOM; the subassembly owns both leaf components so the expanded-subassembly
leaf-only contract is exercised. A dedicated location/reason, exact BOM and
operation shapes, and approval of the two deterministic inert MO IDs are also
required. This command does not provision or seed them. Provisioning, exact
stock-adjustment apply/reversal, and cleanup must first pass through the
separate checkpointed fixture workflow before the higher-risk canary stock
stage runs.
Ordinary-domain canaries are release tests, not expiring runtime permissions.
Every live canary requires separate operator approval for an exact inert
resource; none runs merely because a safe-write gate is open. The approved
inactive-product price fixture must start inactive with no price rows, and its
two exact pricing schemes must be safe for disposable test prices:
```bash
npm run probe:domain -- prices "inactive-product-id" --approve-external-write
```
This probe exercises the production price serializer (including server-assigned
row IDs), stale timestamps, preservation, remove/clear, and cleanup, then emits
release evidence. Product-group and MO-serial release canaries use the same
entry point with `product-groups` or `mo-serials`, exact
They prove full-array preservation/compensation or serial/inventory net-zero
restoration respectively. A passing result does not open a runtime gate or make
an adapter supported; release still requires a code change and normal review.
Product-group configuration and variant creation are supported under the master
safe-write gate.
Concurrency, compensation and response-loss paths have automated tests.
Retain the original preview token and idempotency key through apply and replay;
never regenerate a create after an uncertain result.
### Look Up Serial Numbers
```
Show all in-stock serial numbers for product "Widget Pro"
```
### Create Stock Transfer
```
Transfer 50 units of "Widget Pro" from "Main Warehouse" to "Retail Store"
```
## API Features
### Filtering
Most list operations support filtering. For example:
```
list_products with name="Widget" and isActive=true
```
### Pagination
Large result sets are paginated. Use `skip` and `count` parameters:
- `count`: Number of records to return (max 100)
- `skip`: Number of records to skip
**Note:** The default page size is 20 records. Always specify `count` when you need all records.
### Sorting
All list operations support sorting:
- `sort`: Property name to sort by (e.g., "name", "modifiedDate", "orderDate")
- `sortDesc`: Set to `true` for descending order
```
list_products with sort="modifiedDate" and sortDesc=true
```
### Total Count
To get the total number of matching records (useful for pagination UI), use `includeCount`:
```
list_sales_orders with status="Open" and includeCount=true
```
Response includes:
```json
{
"data": [...],
"totalCount": 42
}
```
### Smart Search
Some endpoints support `smart` parameter for fuzzy searching across multiple fields:
```
list_customers with smart="acme" // Searches name, email, phone
list_sales_orders with smart="SO-2025" // Searches order fields
```
### Includes
Use the `include` parameter to fetch related data:
```
get_sales_order with include=["customer", "lines", "lines.product"]
get_product with include=["inventoryLines"]
```
The `2026-04-13` API rejects `itemBoms.childProduct` even though nested includes
work for relationships such as `productOperations.operationType`. The BOM tools
therefore fetch the product/BOM once, then resolve child products with a
rate-limited concurrency of four. Failed child enrichment produces warnings and
never drops a BOM row.
### Partial Updates
`upsert_sales_order` and `upsert_manufacturing_order` support safe partial updates when `id` is provided:
- Existing fields and lines that are not mentioned are preserved.
- Line patches merge by explicit line ID, or by product ID when there is exactly one matching line.
- Sales order lines listed in `deleteLineIds` are removed.
- Manufacturing order input lines listed in `deleteInputLineIds` are removed.
- Serial numbers can be patched without rebuilding the whole order manually.
### Concurrency Control
When updating records, include the `timestamp` field from the original record to prevent conflicts.
## Rate Limiting
The inFlow API has a rate limit of 60 requests per minute. This server implements:
- **Token bucket rate limiting**: Automatically paces requests to stay within limits
- **Automatic retries**: Retries on 5xx errors and rate limit (429) responses with exponential backoff
- **Configurable limits**: Override via `INFLOW_RATE_LIMIT` environment variable
## Error Handling
The server returns descriptive error messages from the inFlow API. Common errors include:
- **401 Unauthorized**: Invalid API key
- **404 Not Found**: Resource doesn't exist
- **409 Conflict**: Timestamp mismatch (record was modified)
- **429 Too Many Requests**: Rate limit exceeded (automatically retried)
## Development
```bash
# Watch mode for development
npm run dev
# Build for production
npm run build
# Run the server
npm start
# Run tests
npm test
# Run tests in watch mode
npm run test:watch
# Run tests with coverage
npm run test:coverage
```
## API Documentation
- [inFlow API Documentation](https://cloudapi.inflowinventory.com/docs/index.html)
- [inFlow API Support Guide](https://www.inflowinventory.com/support/cloud/inflows-api)
## License
MIT
TDQS
Scored across 86 tools
With 86 tools, many overlap: upsert_* and set_* pairs exist for most entities, multiple serial-lookup tools (search_serial_number, list_serial_numbers, get_product_serials, list_all_serials), and receipt tools overlap with purchase-order updates. An agent must parse long descriptions to distinguish legacy vs preview-first replacements, and some boundaries remain unclear.
Names consistently use snake_case verb_noun conventions (list_products, get_sales_order, set_webhook), with predictable prefixes for list/get/upsert/set/delete/remove. Minor legacy vs newer 'set' verbs coexist but the pattern is stable.
86 tools is far beyond a well-scoped set; the server includes many deprecated legacy writes plus preview-only replacements that cannot yet apply, inflating the surface. This is an extreme mismatch for an inventory MCP and will burden tool selection.
Core transaction types have create/read/update paths, but many admin/lookup entities (locations, categories, pricing schemes, payment terms, tax codes, currencies, custom fields, team members) are list-only with no create/update/delete. Also many set_* write tools are preview-only and apply remains unavailable, leaving significant gaps for full lifecycle operations.