Skip to main content
Glama

fabplane-cli

Command line, MCP server and typed TypeScript client for fabplane.com: orgs, personal API tokens, carts (shopping lists / BOMs with per-item purchase destinations) and the parts inventory. It also talks to the local fabPlane desktop app (the fabdesk daemon).

  • fabplane: a CLI with human output by default and --json for scripts

  • fabplane mcp: a stdio MCP server for Claude Code, Codex, Cursor and other agents

  • import { FabplaneClient } from "fabplane-cli": the same API as a library

Docs: https://fabplane.com/docs/cli · API reference: https://fabplane.com/docs/api · MIT licensed.

Install

npm i -g fabplane-cli     # installs the `fabplane` command
npx fabplane-cli help     # or run it without installing

Until the first npm release, install from GitHub:

npm i -g github:fabPlane/fabplane-cli

Node.js 20 or newer is required.

Related MCP server: a2a-mcp-bridge

Sign in

fabplane login                       # browser device flow: prints a URL and code, waits for approval
fabplane login --token fpk_…         # store a personal API token instead
fabplane whoami
fabplane logout

Credentials are resolved in this order:

What

Source

token

--token on login, then FABPLANE_TOKEN, then the credentials file

API origin

--origin, then FABPLANE_API_ORIGIN, then the origin you last logged in to, then https://api.fabplane.com

org

--org, then FABPLANE_ORG, then the default set with fabplane orgs use, then your personal org

The credentials file is $XDG_CONFIG_HOME/fabplane/credentials.json (default ~/.config/fabplane/credentials.json; %APPDATA%\fabplane\credentials.json on Windows). It is written atomically with mode 0600 and keeps one profile per API origin, so you can be signed in to production and to a self-hosted or development API at the same time. FABPLANE_CREDENTIALS_FILE overrides the path.

Personal API tokens (fpk_…) suit CI and agents: create one with fabplane tokens create <name>, then set FABPLANE_TOKEN. logout does not revoke fpk_ tokens; use fabplane tokens revoke <id>.

To use an API other than production, set FABPLANE_API_ORIGIN (for example http://localhost:4000 for a local fabplane API) or pass --origin. Password login (fabplane login --email … --password …) exists only for local/development accounts and is refused against production.

Commands

Every command accepts --json (machine output), --org <id|slug> and --origin <url>. fabplane help <command> prints the details.

Command

What it does

login [--token fpk_…] [--origin URL] [--no-browser]

Sign in (device flow) or store a token

logout

Forget stored credentials; revokes a device session

whoami

User, API origin, token source and default org

dashboard [--open]

Print (or open) the web dashboard for the current API

orgs list

Your orgs; * marks the default

orgs create <name> [--slug s]

Create an org (you are owner)

orgs use <id|slug>

Set the default org for this API origin

orgs members

Members of the org

orgs invite [--email e] [--role member|admin] [--days n]

Create an invite link (admin)

orgs invites

Pending invites (admin)

orgs role <userId> <owner|admin|member>

Change a member's role

orgs accept <token|url>

Accept an invite

orgs joinable / orgs join <id>

Orgs your verified email domain may join, and joining one

tokens list / tokens create <name> [--days n] / tokens revoke <id>

Personal API tokens (the secret is shown once)

destinations list

Built-in fab houses and distributors plus the org's custom ones

destinations add --name N --url U [--kind k] [--base builtin:digikey]

Add a custom destination, e.g. a regional DigiKey site

carts list [--repo URL] [--project ID]

Carts, filtered by git repo or client project id

carts show <cartId>

A cart, its items and per-destination totals

carts create <name> [--repo URL …] [--project ID] [--notes T] [--fab builtin:jlcpcb]

Create a cart

carts add <cartId> --mpn M --qty N [--dest ID] [--refs R1,R2] [--sku S] [--price P --currency USD] …

Add one item

carts import <cartId> <file.json|file.csv|-> [--replace] [--source S]

Add items from JSON, JSONL or a BOM CSV (500 per request); --replace replaces all items

carts export <cartId> [--dest ID] [-o file.csv]

CSV export, optionally for one destination

carts delete <cartId>

Delete a cart

inventory list [--q text] [--category c] [--location l] [--tag t] [--limit n] [--cursor c] [--all]

Search inventory

inventory show <id>

One item with attributes and images

inventory add --name N [--mpn …] [--qty n] [--location l] [--tag t …] [--attr k=v …] [--image photo.jpg …] [--external-id id] [--server-ai]

Add an item, optionally with photos

inventory import <file.jsonl|-> [--source S]

Bulk upsert, 500 items per request, keyed by externalId

inventory adjust <id> <delta> [--reason r]

Atomic stock change, e.g. -5

inventory delete <id>

Delete an item and its images

mcp [--desktop]

Run the stdio MCP server

desktop status / desktop projects / desktop tools

The local fabPlane desktop app

desktop call <tool> [json-args] [--project ID] [--sync]

Call a desktop tool

api <METHOD> <path> [json|-] [--anonymous]

Raw request to any endpoint (escape hatch)

version, help [command]

Exit codes: 0 success, 1 API or runtime error, 2 usage error.

Carts and destinations

Each cart item has a destination: a built-in (builtin:jlcpcb, builtin:pcbway, builtin:oshpark, builtin:digikey, builtin:mouser, builtin:lcsc, builtin:arrow, builtin:farnell, builtin:aliexpress, builtin:amazon, builtin:manual) or one of the org's custom destinations. Items without one go to the cart's fab house (--fab, JLCPCB by default).

carts import reads CSV headers case-insensitively and understands common BOM names: qty, designator, mfr, manufacturer part number, LCSC Part (as sku), value, footprint, unit price, destination, status, notes. A row with no quantity column counts its designators.

fabplane carts create "Rev B" --repo https://github.com/acme/sensor-board
fabplane carts import <cartId> bom.csv
fabplane carts add <cartId> --mpn STM32G031K8T6 --qty 10 --dest builtin:lcsc
fabplane carts export <cartId> --dest builtin:lcsc -o lcsc.csv

Inventory from photos: extract locally, send fields

The API stores what you send; it does not run AI on uploads. Setting serverAiProcessing: true (--server-ai on the CLI) is answered with HTTP 501 server_ai_unavailable and nothing is stored. Clients are expected to run a model locally (a vision model on the photo of a reel, bag label or datasheet), extract the fields, and send them as structured data with the photo attached:

# e.g. after a local model read the label of a reel:
fabplane inventory add --name "10k 0603 resistor" --mpn RC0603FR-0710KL --manufacturer Yageo \
  --qty 5000 --unit pcs --location "Lab / drawer A3" --tag passive \
  --attr resistance=10k --attr tolerance=1% --attr package=0603 \
  --source my-scanner --external-id reel-0042 --image reel.jpg

For batches, write one JSON object per line and import them. Rows are upserted by (source, externalId), so re-running an import updates instead of duplicating:

{"name":"NE555 timer","mpn":"NE555P","manufacturer":"TI","quantity":25,"location":"Bin 4","externalId":"bin4-ne555","attributes":{"package":"DIP-8"}}
{"name":"100nF 0402 capacitor","mpn":"GRM155R71C104KA88D","quantity":10000,"externalId":"reel-0107"}
fabplane inventory import parts.jsonl --source my-scanner

Rows without an externalId get one derived from name, MPN, manufacturer, SKU and location.

MCP server

fabplane mcp serves these tools over stdio. They act on your default org unless the agent passes orgId:

Tool

org_list

Your orgs (ids, slugs, roles)

destination_list

Purchase destinations for cart items

cart_list, cart_get, cart_create

Find, read and create carts (by repo or project id)

cart_add_items, cart_update_item, cart_remove_item

Edit cart items

inventory_search, inventory_add, inventory_adjust

Search, add and count stock

fabplane mcp --desktop also exposes desktop_status, desktop_projects and desktop_call_tool, which reach the fabPlane desktop app running on the same machine.

Sign in first (fabplane login) or pass FABPLANE_TOKEN in the server's environment.

Claude Code

claude mcp add fabplane -- npx -y fabplane-cli mcp
# with a token and the desktop tools:
claude mcp add fabplane -e FABPLANE_TOKEN=fpk_… -- npx -y fabplane-cli mcp --desktop

Codex (~/.codex/config.toml)

[mcp_servers.fabplane]
command = "npx"
args = ["-y", "fabplane-cli", "mcp"]
# env = { FABPLANE_TOKEN = "fpk_…", FABPLANE_ORG = "my-team" }

Cursor (.cursor/mcp.json or ~/.cursor/mcp.json)

{
  "mcpServers": {
    "fabplane": { "command": "npx", "args": ["-y", "fabplane-cli", "mcp"] }
  }
}

With a global install, use "command": "fabplane", "args": ["mcp"] instead of npx.

Library

import { FabplaneClient, FabplaneApiError, resolveAuth, resolveDefaultOrg, dashboardUrlFor } from "fabplane-cli";

const client = new FabplaneClient({ token: process.env.FABPLANE_TOKEN }); // origin defaults to https://api.fabplane.com
const orgId = await resolveDefaultOrg(client); // FABPLANE_ORG, else your personal org

const { cart } = await client.createCart(orgId, { name: "Rev B", repos: ["https://github.com/acme/board"] });
await client.addCartItems(orgId, cart.id, [{ mpn: "NE555P", quantity: 4, refs: ["U1", "U2", "U3", "U4"] }]);

try {
  await client.adjustInventory(orgId, "item-id", -10, "built 10 boards");
} catch (err) {
  if (err instanceof FabplaneApiError && err.code === "conflict") console.log("not enough stock");
  else throw err;
}

console.log(dashboardUrlFor(client.origin)); // https://app.fabplane.com/dashboard
  • Every API operation is a method named after its OpenAPI operationId (listOrgs, createCart, replaceCartItems, listInventory, bulkUpsertInventory, uploadInventoryImage, …) and resolves to the JSON body the API documents, e.g. { orgs } or { items, nextCursor }. 204 answers resolve to undefined; exportCartCsv resolves to CSV text.

  • Also available: startDeviceLogin, pollDeviceToken, waitForDeviceToken, me, logout, getSettings, putSettings, publicCatalog, config, sendPush, getPushJob, and raw/request for anything else.

  • Failures throw FabplaneApiError with status, code (the API's error field), message, body.

  • Images: createInventoryItem(orgId, input, [{ data, contentType, filename }]) sends multipart; data may be a Uint8Array, ArrayBuffer or Blob.

  • Pass fetch to the constructor to inject your own implementation (tests, proxies).

  • fabplaneTools is the MCP tool list as plain objects (name, title, description, zod inputSchema shape, annotations, handler(client, args, { orgId })) for embedding in another MCP server; createFabplaneMcpServer() builds a ready McpServer.

  • CredentialStore and resolveAuth() read and write the same credentials file as the CLI.

The desktop app

import { FabdeskClient } from "fabplane-cli";

const desk = new FabdeskClient(); // finds daemon.json; or { baseUrl, token }, or FABDESK_URL + FABDESK_TOKEN
console.log(await desk.health(), await desk.projects());
const tools = await desk.toolManifest();
const result = await desk.callTool("board_stats", {}, { projectId: "…", sync: true });

FabdeskClient looks for daemon.json in FABDESK_HOME, then in the desktop app's data folders (~/Library/Application Support/{fabPlane,fabdesk,fabPlane Dev,fabdesk-dev} on macOS, %APPDATA%\… on Windows, $XDG_CONFIG_HOME/… on Linux), preferring a daemon whose process is alive. It covers health, projects, project, readFile, threads, createThread, messages, sendMessage, liveRuns, toolManifest, callTool, jobs, job, waitJob, settings and authState.

API spec sync

spec/openapi.json is a snapshot of the API's OpenAPI 3.1 document and src/generated/operations.ts lists its operations. The Sync API spec workflow runs every Monday at 03:00 UTC (and on demand): it fetches /v1/public/openapi.json, regenerates both files and opens or updates a pull request when something changed, listing operations that have no FabplaneClient method yet. A test fails while any operationId lacks a method. If the API does not serve the document yet (404), the job ends without changes.

npm run sync-spec                 # fetch from FABPLANE_API_ORIGIN (default https://api.fabplane.com)
npm run sync-spec -- --offline    # regenerate from the committed snapshot only

spec/fabdesk-tools.json is a snapshot of the desktop app's tool manifest (names, toolsets, descriptions and JSON-schema inputs, as served by GET /tools/manifest). The fabdesk repository refreshes it with a pull request here when its tools change.

Contributing

npm ci
npm run build      # tsc → dist/
npm test           # compiles src + test and runs node --test
bun test           # the same tests straight from TypeScript

Tests run against a small in-memory fake of the API (test/fake-api.ts) and of the desktop daemon (test/fake-daemon.ts); no network access is needed. Keep runtime dependencies to @modelcontextprotocol/sdk and zod (imported as zod/v4), and keep src/ free of Bun-only APIs: the package must run under plain Node. Bun workspaces that vendor this repo get src/index.ts through the bun export condition.

Publishing

Releases go to npm from GitHub Actions (.github/workflows/publish.yml), which needs an npm token that has not been added yet:

  1. On npmjs.com, create an automation (or granular publish) token that can publish the fabplane-cli package.

  2. Add it as the NPM_TOKEN repository secret: Settings → Secrets and variables → Actions.

  3. Bump version in package.json and src/version.ts, then publish a GitHub release or push a v* tag (e.g. v0.1.1). The workflow builds, tests and runs npm publish --provenance.

Without the secret the workflow fails on its first step with an error asking for it, and publishes nothing. Until then, install from GitHub: npm i -g github:fabPlane/fabplane-cli.

License

MIT

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    MCP server that provides pre-checkout basket tools and a local API/viewer, enabling agents to research products and manage a shopping cart through natural language.
    1
    MIT
  • F
    license
    A
    quality
    B
    maintenance
    MCP server that exposes read-only NetSuite tools for querying warehouse stock levels and order status, using mock data locally and a pluggable client interface.
    2
    -