GunStore-POS Admin MCP
A firearms-store (OpenGunstore POS / Frappe-ERPNext) MCP server: it reads and changes inventory, orders, compliance records, integrations and accounting — with confirm=true gating every consequential write.
Generic Frappe backbone — list/get/create/update/delete/submit/cancel any doctype, describe schemas, run whitelisted methods and reports.
Integration settings — read/update FFL, FastBound, RSR, Payroc, WooCommerce, ShipStation, GunBroker config (credentials stripped; GunBroker environment/money fields refused outright).
Inventory & items — item/stock lookups, in-stock serials with per-gun prices, receive goods (Purchase Receipt + FFL acquisitions + FastBound push), non-serialized stock add/set, RSR catalog search, promote-to-item, backfill.
Compliance — ATF eZ-Check FFL verification, 4473 start/manager-override/customer-transfer, bound-book reconcile and serial corrections, firearm cost fixes.
Counter/dealer/web orders — pending queues, dispose (FFL transfer disposition + stock-out + FastBound), record payments, ShipStation label push or manual ship, cascading cancel with refund.
Consignments — At-Dealer queue, dealer shippability, pickable serials, create/ship/ShipStation push/mark shipped, settlement invoice retry, returns, cancel, price edits.
WooCommerce — list/delist an item or ONE serial, per-gun titles, reconcile, connection probes.
GunBroker — probe (reports sandbox vs live), listing status; push/end/reprice listings only when explicitly opted in.
Distributor drop-ship — orders & confirmation queue, catalog search, quotes, live availability, FDS precheck, fulfillment options, draft orders; confirm/cancel/reroute behind an opt-in flag.
Reports (read-only, also in CPA mode) — Sales Report, GL entries, P&L/balance sheet/trial balance, tax liability, AR/AP ageing, Firearms-In-Stock, card charge void/refund.
Also available per README — customers, CRM, gunsmith jobs, memberships, shop-floor stocktake/cash drawer/storage, HR, range board, file upload.
Safety — destructive/high-consequence actions need
confirm, password/credential fields are never transmitted, schema doctypes are read-only, and a 30-tool read-onlycpamode plus OAuth remote connector exist.
Integrates with the GunStore-POS Frappe backend to manage inventory, settings, reports, and compliance operations such as RSR sync, FastBound integration, and ATF FFL verification.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@GunStore-POS Admin MCP show me the current FFL settings for the store"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
OpenGunstore MCP
A local MCP server that wraps the OpenGunstore Frappe REST API, so you can read and change a store's settings and content from Claude or Codex — toggle integration config, edit item pricing/listing, fix records, trigger RSR/FastBound/ATF operations, run the Firearms-In-Stock report.
How it works
A small generic backbone (
frappe_*tools) covers every doctype and every whitelisted method — present and future. A curated layer makes the frequent settings/sync/report ops one call.Talks to Frappe over HTTPS with token auth (
Authorization: token key:secret).Safety: delete/cancel/submit and destructive methods — including this domain's own high-consequence verbs (dispose / push / charge / consolidate / ship / return / receive / sold / settle / onboard) — require
confirm=true; password/credential fields are never transmitted (set those in Desk); schema/permission doctypes are read-only.
Related MCP server: Gunsnation MCP
Setup
1. Generate a Frappe API key
In Desk as the user you want to act as (Administrator): top-right avatar → My Settings → API Access → Generate Keys. Copy the API Key and API Secret (the secret is shown only once).
2. Configure credentials
cp .env.example .env
# edit .env: FRAPPE_BASE_URL, FRAPPE_API_KEY, FRAPPE_API_SECRET.env is git-ignored. Point at a dev site first (http://dev.localhost:8000) to test,
then switch to your live POS URL (e.g. https://pos.example.com).
3. Install
uv sync # creates .venv and installs deps4. Register with your agent
/path/to/opengunstore-mcp below is wherever you cloned this repo (the package is
at the repo root). The server loads .env by its own file path, so credentials
never go in the agent config.
Claude Code — add to .mcp.json (project) or run claude mcp add:
{
"mcpServers": {
"gunstore-pos": {
"command": "uv",
"args": ["run", "--directory", "/path/to/opengunstore-mcp", "gunstore-mcp"]
}
}
}Claude Desktop — the same block in claude_desktop_config.json.
Codex — add to ~/.codex/config.toml, then restart Codex (it reads MCP
servers at startup):
[mcp_servers.gunstore-pos]
command = "uv" # or an absolute path to uv if it isn't on Codex's PATH
args = ["run", "--directory", "/path/to/opengunstore-mcp", "gunstore-mcp"]
startup_timeout_sec = 120Verify with a quick stdio handshake (initialize + tools/list) or just list
tools from within Codex.
Secrets stay in .env (loaded by the server), not in the agent config.
Tools
Tool reference, organised by task (safety notes and alternatives included): TOOLS.md
188 tools total: 10 counter orders + 5 customers + 5 gunsmith/membership + 6 CRM + 15 compliance/store-admin + 11 generic + 69 curated + 16 distributor + 7 CPA reports + 30 shop-floor + 7 HR + 7 Range; 173 register by default. Three opt-in sets are held back: the 4 distributor queue actions (GUNSTORE_MCP_DISTRIBUTOR_ACTIONS=1), the 4 GunBroker write actions (GUNSTORE_MCP_GUNBROKER_ACTIONS=1) and the 7 Range tools (GUNSTORE_MCP_RANGE=1, only for a store with the Range extension). None is registered otherwise — an absent tool cannot be talked into firing.
Modes: GUNSTORE_MCP_MODE=cpa starts a read-only accountant surface —
exactly 30 tools (the write surface is never registered), a per-name read-only
method allowlist at the client layer, and the 7 integration Settings doctypes
blocked from reads. Default (full) is the whole surface. Register a second
server entry (e.g. gunstore-pos-cpa) with the same command plus
"env": {"GUNSTORE_MCP_MODE": "cpa"} to run both side by side.
Standalone CPA install
To install only the read-only accountant surface (no full server) — e.g. on a second machine or for an analyst agent:
git clone git@github.com:Old-Steel-Arsenal/opengunstore-mcp.git ~/opengunstore-mcp
claude mcp add gunstore-pos-cpa --scope user \
--env GUNSTORE_MCP_MODE=cpa \
--env FRAPPE_BASE_URL=https://pos.example.com \
--env FRAPPE_API_KEY=<key> \
--env FRAPPE_API_SECRET=<secret> \
-- uv run --directory ~/opengunstore-mcp gunstore-mcpProcess env beats
.env: the server loads.envwithoverride=False, so env vars set in the registration win. A standalone install needs no.envfile at all — and the same checkout can serve several entries with differentFRAPPE_BASE_URL/ mode combinations (e.g. a dev-site instance).Verify: after connecting,
tools/listmust show exactly 30 tools and the server namegunstore-pos-cpa. A misspelled/unknown mode value refuses to start (fail-closed) rather than silently degrading to the writable surface.Security boundary — read before handing this to a third party: the three-layer gate restricts the MCP call layer only. The API key/secret in the registration is whatever that Frappe user can do — with a System Manager key, anyone who extracts it can write via plain REST, MCP gate or not. For a third-party (e.g. an outside accountant's machine), create a dedicated Frappe API user with read-only roles and register with that key pair.
Generic backbone
Tool | Purpose |
| list any doctype (filters / fields / limit; |
| fetch one doc (Single: |
| fields incl. custom fields; flags password fields |
| create (credential fields stripped) |
| update (credential fields stripped) |
| delete — needs |
| submit a draft — needs |
| cancel a submitted doc — needs |
| call any whitelisted method by dotted path (credential, signing, webhook and legacy-alias endpoints refused; consequential ones need |
| search the POS endpoints the signed-in user can run: arguments, roles, whether |
| run a Script/Query report |
Curated
Tool | Purpose |
|
|
| typeahead item search / stock per item / in-stock serials + per-gun prices |
| the Firearms In Stock report |
| what |
| parcels on their way (read-only) / receive one against its Distributor Order or store invoice (booking needs |
| Purchase Receipt + FFL acquisitions + FastBound push ( |
| non-serialized stock add / absolute set ( |
| gunsmith flag on a Serial No ( |
| RSR-only catalog search ( |
| RSR catalog row → sellable Item / backfill Item fields ( |
| FB probe / bound-book reconcile (apply needs |
| dry-run-only bound-book reconcile (read-only; also in cpa mode) |
| ATF eZ-Check verifies ( |
| store probe / list / delist / reconcile an Item — all take |
| list / delist ONE gun (SKU |
| per-gun Woo listing title (writes |
GunBroker channel | the environment, credential and money fields of |
| GunBroker probe — the reply's |
| list ONE gun as a fixed-price Buy Now / end its listing (opt-in via |
| reprice / replace photos / sync a model's quantity (0 ends it) / lift the "may already be sold" flag on a live listing (opt-in + |
| open GunBroker orders (read-only; also cpa) / record the banked payment / assign units, refunds, shipment and tracking push, invoice retry / book the dispositions ( |
| run the order poll now (opt-in + |
| the Pending Order queue: counter/dealer rows + paid web orders (read-only; consignments live in |
| book the FFL transfer dispositions — stock-out + FastBound push ( |
| Payment Entry against an unpaid submitted invoice; Zelle needs |
| Card charges on a Sales Invoice that can still be voided or refunded (read-only) |
| Void a whole card charge before it settles; the invoice owes it again ( |
| Refund part or all of a card charge: |
| Book or release a void/refund the processor never confirmed, after checking its portal ( |
| cancel a submitted counter/dealer order — cascades disposition/stock/FastBound/ShipStation reversal + refund ( |
| ShipStation label push ( |
| outbound-consignment reads: At-Dealer queue / shippable FFL dealers / pickable serials / settlement queue |
| build / dispose+ship / ShipStation push / manual-ship w/ tracking ( |
| settlement-invoice retry / take unsold guns back / cancel line-or-draft ( |
| change cost / MSRP on guns out on consignment ( |
| 4473 kickoff / stuck-sale manager override / customer transfer ( |
| web-order pickup, invoice retry, refund reconcile, antique retire, backstop, stuck reservations ( |
| why the card terminal will not charge: switch vs readiness, live probe, terminals (read-only) |
| multipart file upload → File doc, optionally attached to a doctype+name / Attach field |
Counter orders and register tickets (tools/orders.py)
Tool | Purpose |
| check an order (serials, tag prices, costs, tax, price approval; read-only) / take a counter, phone or transfer order / edit a pending one ( |
| fulfil from stock, re-source a line, open the pickup ticket, resend the agreement ( |
| tickets parked at the register (read-only) / open, tender, card start/poll/cancel, submit a ticket ( |
| counter terminal charge on a Sales Invoice ( |
| buy a gun from a walk-in seller and pay them now ( |
Customers, gunsmith, memberships (tools/customers.py, tools/services.py)
Tool | Purpose |
| standing, alerts, family, IDs, agreements (and what a cart still needs signed) / find by email, phone, ID or name (read-only) |
| merge, override a ban, set status, add an alert / send an agreement by email, tablet or for paper / void or record a paper signature ( |
| the repair queue (read-only) / take a gun in / start, ready, notify, payment, pickup, cancel, refund, log in the bound book ( |
| plans, roster, one customer's (no confirm; never on a read-only surface) / start, invoice, renew, retry the card, auto-renew, change card, cancel, drop a Pending sign-up ( |
CRM (tools/crm.py)
Tool | Purpose |
| leads and deals by email, name, status, owner, or waiting for a reply / one deal with its notes, tasks and customer card (read-only) |
| the want list: |
| convert a lead, change a status or owner / add a note or task / save a deal as a Create Order draft ( |
Compliance repairs and store admin (tools/compliance.py)
Tool | Purpose |
| re-check a ticket's 4473 and say the next step / attest a paper 4473 (remote only), redo or abandon ( |
| compare guns with the bound book (read-only) / match, apply the differences, push a correction ( |
| eZ Check a dealer customer's FFL ( |
| per-gun cost (read-only) / correct a cost or a mistyped serial ( |
| the morning briefing and needs-attention counts / check a receive before booking it (read-only) |
| a gun's or model's gallery (read-only) / add, reorder, set primary, caption, remove ( |
| installed extensions and updates (read-only) / refresh, enable, disable, install, two-step update (System Manager; |
Shop floor: stocktake, cash drawer, storage locations (POS 1.5.0-beta.15)
Every write needs confirm=true; there is no registration gate (they are the same counter actions the POS pages offer, role-checked per call and audited on the remote connector). Firearms are never adjusted by a stocktake and storage moves touch no stock or books.
Tool | Purpose |
| list stocktakes / one count's scans + expected rows / counted-vs-system variance (read-only; counts + variance are also on the cpa surface) |
| start (whole store or scoped) / cancel an open count ( |
| record a serial or UPC scan / type a quantity / tick a gun by hand / remove an entry ( |
| posts ONE Stock Reconciliation for the non-serialized differences, saves the firearm report ( |
| the drawer page (expected cash + lines) / what a close WOULD do (read-only) |
| daily counts / deposits, cash from the bank, expenses, seller payouts / every cash movement with who and the running balance / the Cash Drawer Weekly report (read-only; also on the cpa surface) |
| count the drawer, close POS shifts, book the over/short or first-count entry ( |
|
|
| cancel an entry or the latest close / change the method of / re-book an old-way seller payment (first payments go through the receipt) ( |
| the store map ( |
| zone management ( |
| assign a gun / put units into a location / undo it / say where sold units came from ( |
Distributor direct-ship (tools/distributor.py)
Tool | Purpose |
| Distributor Orders / the confirmation queue / local catalog typeahead / probe one house (read-only) |
| cost, MAP and block flags / live stock re-check / will it ship to that dealer / per-line options for one web order (read-only) |
| Draft restock order(s) shipped to the store ( |
| the 4 queue actions: place / cancel / re-route / fix an FDS hold (opt-in via |
Frappe HR (tools/hr.py; needs the hrms app on the site)
Attendance, shifts and leave only; payroll is out of scope. Every write needs confirm=true, and there is no registration gate. On a site without hrms every tool answers "Frappe HR (hrms) is not installed on this site".
Tool | Purpose |
| active staff with shift, holiday list, approver, whether a User Permission limits their login, and today's last clock-in / check-ins and Attendance per day with totals (range capped at 62 days) / leave balances or the Open Leave Applications (read-only; nothing personal is returned) |
| days Present / Half Day / Absent / On Leave and working hours per employee for a period, with the count of days that have check-ins but no Attendance yet (read-only; also on the cpa surface) |
| approve or reject an Open Leave Application, a reject needs a reason ( |
|
|
| make an existing login an employee in one step: Employee, User Permission rule, shift, holiday list and the Leave Policy Assignment for the current anniversary year ( |
Range board (tools/range_board.py, opt-in via GUNSTORE_MCP_RANGE=1)
Tool | Purpose |
| one day's board / lanes free at a time / a customer's bookings, each with its guests: service, discount, waiver, checked in, paid, invoice (read-only) |
| who is on a booking, what each owes and who has paid, and the tickets Check out would open (read-only) |
| book a lane or block / change a booking / cancel with a reason ( |
CPA reports (read-only; registered in both modes)
Tool | Purpose |
| the POS Sales Report, payload passed through unchanged (views Order / Order Detail / Product; channel POS/Web/Manual) |
| the POS Inventory Receipts report — everything that entered stock in a period (per unit / per serial, or a category × receipt-type summary), straight from the stock ledger; classes Purchase / Trade-in / Consignment / Intake / Return / Adjustment / Revaluation (cost correction, 0 units), with No-cost / No-A&D flags |
| GL rows for a date range ( |
| P&L / Balance Sheet (Date Range) / Trial Balance (fiscal-year auto-resolved) |
| sales-tax liability roll-forward from the GL — accounts resolved from the default sales-tax template, vouchers bucketed fail-closed, cent-exact identity asserted |
| every Payroc card transaction for a date range (≤31 days), read live from the gateway — counter and Woo web orders, sales / refunds / declines, portal refunds and voids included — each matched to the POS with disagreement flags; card type + last 4 only, plus the cardholder name. Needs an OpenGunstore release carrying |
| aged AR / AP as of a date (Posting Date basis, 30/60/90/120). AP is only meaningful when purchases are recorded as Purchase Invoices in ERPNext — reference only |
Remote connector (OAuth, no API key)
The same server runs as a remote MCP endpoint with GUNSTORE_MCP_TRANSPORT=http.
It is an OAuth resource server; the POS itself is the authorization server
(Frappe v16's built-in OAuth: dynamic client registration + PKCE). A user adds the
connector URL in claude.ai (Settings → Connectors) or Claude Code
(claude mcp add --transport http <name> <url>), signs in to the POS in the browser
and approves. No key is typed anywhere, and the server holds none.
Several stores. Each store's POS serves its own connector, and all of them carry
the same tool names. Give each one a name that says which store it is
(claude mcp add --transport http gunstore-pos-<store> <url>). The server tells the
model which POS host it controls (in its instructions and in every confirm refusal),
so an assistant with two stores connected can check before it writes.
Every request carries that user's bearer token. The server checks it against the POS (
ffl_core.api.connector.connector_identity, cached 60 s; the token is never logged or cached in the clear) and forwards it on every call, so each call runs with the signed-in user's own POS roles.Works with any MCP client that supports OAuth — Claude (claude.ai Connectors, Claude Code
claude mcp add --transport http) and ChatGPT (custom connectors / developer mode, or the Responses APImcptool with the user's token). Each client registers itself with the POS (dynamic client registration), and the audit shows which one made each call.Switched in the POS: Desk → MCP Settings turns the read-only and full servers on or off separately (and owns the OAuth settings they sign in through); a switched-off server tells the user so on every call.
Only connector tokens are accepted: a token the POS issued to a client created by dynamic registration. Tokens of OAuth apps made in Desk are refused.
Every call is audited in the POS (Desk → Connector Audit Log, permanent, rows cannot be deleted; reads included): who, through which connector, which tool, which arguments (secrets masked by key, in filters and inside JSON strings), Started → Success / Failed. The row is written before the tool runs — if it cannot be, the tool does not run. Needs an OpenGunstore release carrying
ffl_core/api/connector.pyand the Connector Audit Log doctype.A user with Restrict IP set cannot use the connector: calls reach the POS from the connector (and ultimately from Claude's servers), never from the user's IP.
cpamode keeps all three read-only layers. The full surface is 172 tools remotely:upload_attachmentreads a path on the server and is never registered there. The distributor queue actions never open remotely (the server refuses to start;distributor_create_draft_orderis not one of them and is there); the 4 GunBroker writes open on the full connector only, where the POS deploy setsGUNSTORE_MCP_GUNBROKER_ACTIONSfrom that store's own GunBroker switch — a store that lists on GunBroker must be able to end a listing. The Range tools open where the POS deploy setsMCP_RANGE=1(a store running the Range extension).Env:
GUNSTORE_MCP_TRANSPORT=http,FRAPPE_BASE_URL(the store's public POS URL, also the OAuth issuer),GUNSTORE_MCP_PUBLIC_URL(https://pos.<domain>/connector/<mode>/mcp, the URL users are given),GUNSTORE_MCP_PORT,GUNSTORE_MCP_MODE; optionalGUNSTORE_MCP_HOST(listen address, default 127.0.0.1) andFRAPPE_INTERNAL_URL(loopback URL of the local POS frontend — calls then go there directly with the site's Host header). It runs on the POS host behind that host's reverse proxy, which maps the public path to/mcpand forwards/.well-known/oauth-protected-resource/<public path>unchanged.Deployment: a container per surface on each store's POS host, behind that host's Caddy — image
ghcr.io/old-steel-arsenal/gunstore-pos-mcp(built on every push to main), deployed by each POS release (opengunstoredeploy/prod/mcp, pinnedMCP_TAG) — seedeploy/README.md.POS side, once per site (OAuth Settings): Show Auth Server Metadata and Enable Dynamic Client Registration on; Skip Authorization off (every user approves). Revoke a user's access in Desk under OAuth Bearer Token.
Website connector (WordPress abilities bridge)
GUNSTORE_MCP_MODE=web turns the remote server into a connector for the store's
WordPress site: people sign in with their POS account exactly as above, and the server
calls the site's Abilities API on
their behalf. It registers no POS tools. Server name gunstore-web; POS connector
surface web_ro (URL https://pos.<domain>/connector/web_ro/mcp; the key predates
writes and is kept so no deployment has to move).
The site is called as one service account (a WordPress user with an application password, HTTP Basic), through core's REST routes:
GET /wp-json/wp-abilities/v1/abilities/<name>for the schema at start, then per call…/<name>/runwith the method WordPress requires for that ability (WP_REST_Abilities_V1_Run_Controller::validate_request_method):GETwith?input[...]=for a read-only ability,DELETEwith the same query input for one marked both destructive and idempotent,POSTwith a JSON body{"input": …}for any other. Nothing is retried.Reads: two gates, both must pass, failing closed: an ability in
WEB_ABILITIESorWEB_ABILITIES_RESTRICTEDbecomes a tool only if the site's metadata also saysannotations.readonly: true. Anything else is skipped and named in the startup log.Writes are opt-in: exactly the abilities listed in
WEB_ABILITIES_WRITE(empty or unset: the connector is read-only and lists the same tools, with the same instructions, as before writes existed). A listed write is bridged whatever the site's annotations say; they only choose the HTTP method. Lists are deployment configuration.One tool per ability, named mechanically (
woocommerce/orders-query→woocommerce_orders_query), with the site's description and input schema. Reads are markedreadOnlyHint; writesreadOnlyHint: false, anddestructiveHintwhen the name containsdelete,update-status,change-campaign-status,sendorbulk, or the site marks the ability destructive (a hint for the client's confirmation prompt, not a gate). A write's description ends "Changes the website. Needs the Website Connector Manager role in the POS."woocommerce/order-add-notealso says that a note withcustomer_note: trueis emailed to the customer.A read called without arguments sends the schema's declared property defaults when it has no top-level default (a GET query cannot carry an empty object, and WordPress refuses a missing one). With no defaults either, the site refuses the empty call and the tool answers by naming its parameters so the client retries with some; an ability that takes no parameters needs a top-level
defaultin its input schema on the site to be callable over GET. APOSTwrite without arguments sends{"input": {}}.It says which site it works on: the server's instructions name the host of
WEB_SITE_URL; read-only when no write is listed, otherwise they say which tools change the live site, who may call them, and to confirm with the user first.Who may call what: the POS reports each user's website role (
connector_identity→web_access:viewer,manageror none). No role: every call is refused. Abilities inWEB_ABILITIES_RESTRICTED(e.g. contact-level data) and every write run only formanager; a viewer gets a plain refusal, not a sign-in prompt.Audited like every connector call: the Connector Audit Log row (surface
web_ro) is written before the site is called; if it cannot be, the site is not called. A call refused for the user's website role is recorded as failed. A write's row keeps what it targets, not what it writes: ids, tag and list changes, statuses and flags as given; an email asj***@example.com; a map (fields,custom_fields) as its key names; any other text or list as its size only ("<29 characters>"). Reads keep their arguments.Errors in plain words: the site rejecting the service account (401), the account lacking a capability (403), an ability gone (404), input refused (4xx), site errors (5xx), timeouts (30 s) and non-JSON pages each get one sentence. A write that times out, loses its connection or gets a 5xx says the change may or may not have been made and to check the site before trying again. The application password never appears in logs, the audit trail, results or errors.
Results over 150,000 characters are cut with a note to use the ability's paging or filter parameters.
Site down at start: the server starts anyway and retries loading the abilities in the background (15 s, doubling to 5 min); tools appear once the site answers. A client that connected meanwhile sees them after reconnecting.
Env, on top of the remote connector's (
GUNSTORE_MCP_TRANSPORT=httpis required; stdio refuses to start):WEB_SITE_URL(https; plain http only for localhost),WEB_SITE_USER,WEB_SITE_APP_PASSWORD, andWEB_ABILITIES/WEB_ABILITIES_RESTRICTED/WEB_ABILITIES_WRITE(comma-separated ability names; at least one in all; an ability in two lists refuses to start). A missing or malformed setting refuses to start and says which. The service account needs the site capabilities each listed ability checks; a missing one shows as a 403 on that tool.
Security notes
Local (stdio) server: uses the key's user (Administrator) = full access. Run it locally only; keep
.envout of git (it is, by default). The remote connector holds no key — see "Remote connector" above.Credentials never transit the MCP: password-type and credential-named fields are stripped from every write. Set secrets in Desk directly.
All writes are logged by Frappe under the key's user (audit trail).
To revoke access, regenerate that user's keys in Desk.
Available Tools
77 toolsadd_stockA
Add stock for a NON-serialized item (Material Receipt Stock Entry). Do NOT use for serial-tracked firearms — those go through receive_goods so the FFL flow runs. confirm=true.
Inputs: qty (required), item_code (required), rate, confirm, +2 more.
ar_ap_summaryA
Aged Accounts Receivable (kind="ar") or Accounts Payable ("ap") as of a date — the standard ERPNext ageing report (Posting Date basis, 30/60/90/120 buckets). Consignment settlement invoices appear in AR under their dealer. company defaults to the default company. Read-only.
Inputs: kind (required), as_on_date (required), company.
atf_verify_fflA
Live-verify an FFL number via ATF EZ Check and upsert an ATF FFL Record. Creates/updates a record, so requires confirm=true.
Inputs: ffl_number (required), confirm.
available_serialsA
In-stock Serial Nos per firearm item, each with its per-gun sell price: {item_code: [{serial, sell_price, ...}]}. Use for 'which units do we have of this model + at what price'. By default mirrors the POS picker: held / consigned-out / consignment-warehouse guns are EXCLUDED (they're not sellable over the counter). Pass exclude_unavailable=false for the RAW list — inventory audits that must also see consigned/held guns. Read-only.
Inputs: item_codes (required), exclude_unavailable.
backfill_from_rsrA
Backfill manufacturer / model / caliber / etc. on existing Items from the RSR catalog (fills only empty fields, never overwrites). item_codes omitted = all RSR-linked items. confirm=true.
Inputs: confirm, item_codes.
boundbook_reconcileA
Reconcile in-stock firearm Serial Nos against the FastBound bound book — find guns FastBound shows as disposed but still Active in our inventory. apply=false (default) is a READ-ONLY dry run that just reports them. apply=true removes them from stock and requires confirm=true. Optional item_ids (FastBound item ids) narrows the scan.
Inputs: apply, confirm, item_ids.
cancel_consignmentA
Cancel a consignment that should never have happened. With line: cancel ONE shipped line ("the gun never left the store" — undoes the disposition; only reachable pre-invoice). Without line: cancel the whole DRAFT document (never-shipped staging record; books/moves nothing). reason is required and recorded. confirm=true.
Inputs: reason (required), consignment_out (required), line, confirm.
cancel_orderA
Cancel a submitted counter/dealer transfer order — the Pending Order page's Cancel Order action. Cascades safely: cancels the invoice (its hooks reverse the dispositions → stock back in → FastBound delete queued), cancels linked Sales Orders, voids the ShipStation order, and books a refund Payment Entry for whatever was actually paid (refund_mode/refund_reference when a refund is due). reason is required and recorded. confirm=true.
Inputs: reason (required), sales_invoice (required), confirm, refund_mode, +1 more.
consignment_dealer_ordersA
The post-sale consignment settlement queue: Sold lines whose settlement invoice isn't booked yet OR whose invoice isn't paid off. Failed rows sort first. Retry a failed settlement invoice with retry_consignment_invoice. Read-only.
Inputs: no named parameters.
consignment_dealersA
Every FFL-dealer Customer with its consignment shippability: FFL number, eZ-Check status, expiry, plus shippable/block_reason (the same gate ship_consignment_out enforces). Use before building a consignment. Read-only.
Inputs: no named parameters.
consignment_queueA
The outbound-consignment "At Dealer" queue — every unfinished Consignment Out (Draft / Shipped) as one card: dealer + shippability, ShipStation state, tracking, line pills, and the action gating the Dispose/Ship buttons read. An INDEPENDENT queue from pending_orders (which excludes consignment invoices). include_closed=true also returns Closed/Cancelled history. Read-only.
Inputs: include_closed.
consignment_serialsA
In-stock (Active) serials pickable for an outbound consignment, annotated with settlement/reference prices (cost = dealer price, msrp = retail) and — for blocked guns — why they can't be picked (blocked rows are returned greyed, not hidden). Filter by item_code and/or a search string. Read-only.
Inputs: limit, search, item_code.
create_consignment_outA
Build an outbound consignment to an FFL dealer. payload: {dealer, company?, notes?, dispose_now?, lines: [{item_code, serial, cost, msrp}]}. dispose_now=0 (default) saves a Draft (guns held, Woo delisted, dispose later from the queue); dispose_now=1 also disposes + best-effort ShipStation push immediately — if a gate blocks (expired dealer FFL) the build still succeeds as Draft with degraded=1. Find candidates with consignment_dealers / consignment_serials. Consequential — confirm=true.
Inputs: payload (required), confirm.
dispose_orderA
Dispose (ship) the firearms on a submitted counter/dealer order: books one FFL transfer disposition per not-yet-disposed serial; each disposition's on_submit issues the stock-out and pushes FastBound. Server blocks it while unpaid or the destination FFL is invalid; idempotent. VERIFY the destination FFL and serials first (the UI makes staff tick exactly that). confirm=true.
Inputs: sales_invoice (required), confirm.
dispose_web_orderA
Dispose the firearms on a paid Woo Online Order (web row). No ShipStation push here — the store's own WooCommerce plugin ships web orders. Idempotent per {serial, order}. confirm=true.
Inputs: woo_online_order (required), confirm.
distributor_catalog_searchA
Typeahead the distributor's catalog by keyword / stock # / UPC / MFG #. Reads the locally-synced catalog, not live stock — use distributor_check_availability before acting on a quantity. Read-only.
Inputs: query (required), limit, distributor.
distributor_check_availabilityA
LIVE quantity/price re-confirm straight from the distributor for the given lines ([{sku, qty}, …]). Changes nothing, but unlike distributor_quote it does reach RSR over HTTP — use it to second-check before confirming an order, not to browse.
Inputs: lines (required), distributor.
distributor_fulfillment_optionsA
Everything needed to decide how to fill ONE web order: per line, the distributor candidates (cheapest first), a LOCAL stock comparison row, and a verdict. Read-only — it plans nothing and orders nothing.
THREE-VALUED FIELDS. if not x is WRONG on both of these; null means "not
determined yet", never "fine":
verdict.fulfillable— true / false / null. Null carries areason:unknown_destination(the destination state is not captured yet — the NORMAL state while an order sits in Pending Route or before the buyer's FFL is on file) orstale_feed(the only quantity that could cover the line came off a feed past its refresh window).restricted_state— true / false / null, same null meaning.
Treat null as HOLD: report the line as undetermined and quote the reason.
Never state that such a line can ship — a restricted item to an unresolved
destination is exactly the case this must not wave through.
CANDIDATE FLAGS: not_carried = that distributor has no catalog row for the
item (listed rather than dropped, so it is not misread as out-of-stock);
blocked = carried but unbuyable (distributor block / vendor approval);
stale = quantity from an aged feed. Unbuyable rows sort to the BOTTOM, so
the first candidate is the one MOST LIKELY to be actionable — but the row's
own verdict and flags decide that, not its position. Ranking is not permission.
MONEY: unit_landed is PER UNIT and goods-only. Freight does not exist until
the order is placed, so shipping is null with shipping_known false. Do not
present unit_landed as a landed cost, and do not multiply it by quantity and
call the result final.
Inputs: woo_online_order (required).
distributor_ordersB
List Distributor Orders (drop-ship / restock purchase orders to RSR). status: Draft | Queued | Placed | Acknowledged | Hold | Partially Shipped | Complete | Cancelled | Error (empty = all). Read-only.
Inputs: limit, status, distributor.
distributor_precheck_fdsA
Ask the distributor whether the transfer dealer named on this FDS order accepts drop-shipped firearms — the cheapest way to avoid an FDS Hold. Reaches RSR over HTTP; changes nothing.
Inputs: do_name (required).
distributor_quoteA
Per-item economics: cost, MAP, MSRP, suggested sell price (the same pricing chain the storefront listing publishes), last-known catalog qty, restricted states, and the block flags. Pass item_code for a stocked item or sku for a raw catalog row. The quantity is the CACHED catalog figure — its freshness is not asserted here. Read-only, no HTTP to the distributor.
Inputs: sku, item_code, distributor.
distributor_route_queueA
The human confirmation queue: Draft Distributor Orders awaiting an operator, plus web orders PARKED before routing (unpaid, missing/expired buyer FFL, incomplete address) with the reason and the destination FFL's expiry. This is the list to read before confirming anything. Read-only.
Inputs: limit.
fastbound_test_connectionA
Probe the FastBound API connection (read-only).
Inputs: no named parameters.
financial_statementA
One of the standard financial statements over a date range: statement = "pnl" (Profit and Loss Statement), "balance_sheet" or "trial_balance". P&L/BS run filter_based_on="Date Range" with the given periodicity (Monthly/Quarterly/Half-Yearly/Yearly). Trial Balance needs the range inside ONE fiscal year (ERPNext requires a fiscal_year filter; it is resolved from the dates — a spanning range is refused rather than guessed). company defaults to the default company. Read-only.
Inputs: to_date (required), from_date (required), statement (required), company, +1 more.
find_itemB
Typeahead search for Items by barcode / item code / name.
Inputs: query (required), limit.
firearms_in_stockA
Run the 'Firearms In Stock' report — in-stock firearms by serial, with acquisition source + FastBound link. Optional warehouse / manufacturer filter.
Inputs: warehouse, manufacturer.
frappe_cancel_documentB
Cancel a submitted document (docstatus 1 -> 2). Requires confirm=true.
Inputs: name (required), doctype (required), confirm.
frappe_create_documentB
Create a document. Credential/password fields are stripped automatically. A few fields are refused outright rather than stripped — see frappe_update_document.
Inputs: values (required), doctype (required).
frappe_delete_documentA
Delete a document. Requires confirm=true. For firearms/Items prefer frappe_run_method('ffl_core.api.item_admin.force_delete', ...) which preserves the A&D audit trail.
Inputs: name (required), doctype (required), confirm.
frappe_describe_doctypeA
List a doctype's fields (custom fields included): fieldname, label, fieldtype, options, reqd. Fields flagged is_password=true can never be written through this MCP — set those in Desk.
Inputs: doctype (required).
frappe_get_documentA
Fetch one document by name. For a Single/Settings doctype, name == doctype (e.g. doctype='RSR Settings', name='RSR Settings').
Inputs: name (required), doctype (required).
frappe_list_documentsA
List documents of any doctype.
filters are Frappe-style: a list of [field, operator, value] (e.g. [["is_firearm","=",1],["status","=","Active"]]) or a simple {field: value} dict. limit defaults to 20; pass limit=0 for all rows. Call frappe_describe_doctype first if unsure of field names.
Inputs: doctype (required), limit, start, fields, +2 more.
frappe_run_methodA
Call any whitelisted server method by dotted path, e.g. 'ffl_integrations.rsr.tasks.sync_catalog_now'. Generic Frappe mutators (frappe.client.set_value/insert/save/delete/bulk_update/...) are refused — use the structured frappe_*_document tools, which enforce the guards. Methods whose name implies a destructive action — or that are on the explicit high-consequence list — require confirm=true.
Inputs: method (required), kwargs, confirm.
frappe_run_reportB
Run a Script/Query Report (e.g. 'Firearms In Stock') and return its columns + result rows.
Inputs: report_name (required), filters.
frappe_submit_documentA
Submit a draft document (docstatus 0 -> 1). Requires confirm=true — submitting can fire side effects (e.g. a FastBound push on FFL Acquisition).
Inputs: name (required), doctype (required), confirm.
frappe_update_documentA
Update fields on a document. Credential/password fields are stripped automatically (set those in Desk). Some fields are refused outright and fail the whole call: on GunBroker Settings the environment switches (sandbox_mode, enabled, base_url_override), the credentials and the money fields — those decide whether a real firearm reaches the real marketplace, so they are a Desk change by a person.
Inputs: name (required), values (required), doctype (required).
gb_listing_statusA
What this POS and GunBroker each think of ONE gun's listing (read-only). "state" is the POS view — unlisted | push_pending | listed | end_pending | sold | sold_unknown | ended — and "remote" is what GunBroker says right now, or null when there is no listing to ask about. Use this before pushing or ending anything if the two might disagree.
Inputs: serial_no (required).
gb_test_connectionA
Probe the GunBroker API connection + credentials (read-only). Reports which environment answered: the "sandbox" key in the reply is true for api.sandbox.gunbroker.com, false for the real marketplace. Read that key before doing anything else — it is how you find out which GunBroker this POS is wired to. It cannot be changed from this server: every write path refuses sandbox_mode, so switching environment is a Desk change made by a person. Needs SYSTEM_ROLES on the POS — the other three gb_ tools need only STOCK_ROLES, so a stock-only API user sees a 403 here alone.
Inputs: no named parameters.
get_settingsA
Read an integration's Settings. which: ffl | fastbound | rsr | payroc | woocommerce | dealer (dealer-portal WooCommerce) | shipstation | gunbroker. Password fields are never returned by Frappe.
Inputs: which (required).
gl_entriesA
General-ledger rows for a date range, optionally filtered by
account / party / voucher_no / voucher_type. Cancelled GL is ALWAYS
excluded (is_cancelled=0 — cancel+amend refund reworks drop out
automatically). Truncation is explicit: when more rows exist than
limit, the result carries truncated:true — never a silent cut, a
silently-shortened ledger reconciles to wrong numbers. limit is
clamped to 1..5000 (0 or empty falls back to 500 — NOT Frappe's
"0 = all" convention); for more rows, paginate by narrowing the date
range. Read-only.
Inputs: to_date (required), from_date (required), limit, party, +3 more.
item_stockA
Stock on hand per item: {item_code: qty} summed across all warehouses. Accepts a list, a JSON list, or a comma-separated string. Read-only.
Inputs: item_codes (required).
manager_override_4473A
Manager force-complete a stuck firearm POS sale whose 4473 WAS genuinely completed in FastBound but never synced back (edited serial, deleted disposition, FastBound down). Mints the Retail Sale disposition(s) marked manual_override with WHO + WHY — auditable; does NOT push FastBound, so reconcile the bound book separately. reason is required. confirm=true.
Inputs: reason (required), invoice_name (required), confirm.
mark_consignment_shippedA
Clear a DISPOSED consignment's Ship step without a ShipStation push (label bought elsewhere / integration off / dealer FFL expired after dispose) — the consignment twin of mark_shipped_manually. Optional tracking_number / carrier hand-record the label so the dealer portal shows it. confirm=true.
Inputs: consignment_out (required), carrier, confirm, tracking_number.
mark_shipped_manuallyA
Clear a DISPOSED order from the Pending Order queue without a ShipStation push (label bought elsewhere / integration off / FFL expired after dispose). Refuses while any firearm is undisposed. confirm=true.
Inputs: sales_invoice (required), confirm.
pending_ordersA
The Pending Order queue, counter + dealer rows: submitted firearm shipments that still need disposing, payment, or a ShipStation push. Web-shop rows come from pending_web_orders. Consignment fulfillment is NOT here — outbound consignments live in their own queue (see consignment_queue); this list explicitly excludes consignment invoices. Read-only.
Inputs: no named parameters.
pending_web_ordersA
Paid WooCommerce web orders awaiting fulfillment — the source='woo' rows of the Pending Order page. Read-only.
Inputs: no named parameters.
promote_to_itemA
Promote an RSR catalog row into a sellable Item (manufacturer / model / caliber / prices / images flow in from the catalog). payload mirrors the promote flow, e.g. {rsr_stock_number, item_code?, item_name?, ...}. confirm=true. Find candidates with rsr_catalog_search.
Inputs: payload (required), confirm.
push_consignment_shipmentA
Push a DISPOSED Consignment Out to ShipStation so staff can buy the outbound label — the At Dealer queue's Ship action. Synchronous, idempotent, persists nothing on API failure (safe to retry). Independent of the Dispose step. confirm=true.
Inputs: consignment_out (required), confirm.
push_serial_to_fastboundA
Push a post-acquisition per-unit correction (manufacturer / importer) for one already-booked firearm to FastBound, editing the bound-book item in place (PUT /Items/{id}). No-op if the gun isn't in FastBound yet. confirm=true.
Inputs: serial (required), confirm.
push_shipmentA
Push a submitted order to ShipStation so staff can buy the label there — the Pending Order 'Ship' action. Synchronous, idempotent, fails closed on an invalid FFL or unpaid order; independent of the Dispose step. confirm=true.
Inputs: sales_invoice (required), confirm.
receive_goodsA
Receive stock via the Receive Goods flow: creates + submits a Purchase Receipt; for firearms it also creates one FFL Acquisition per serial and pushes them to FastBound. payload mirrors the Receive Goods page: {supplier, source, warehouse, acquisition_source, items:[{item_code, qty, rate, serials:[...], manufacturer, model_name, caliber, firearm_type, importer, sell_price, service_need}], ...}. Consequential — confirm=true.
Inputs: payload (required), confirm.
record_paymentA
Record a later payment against a submitted, not-fully-paid Sales Invoice — books + submits a Payment Entry and returns the new balance. amount omitted = the full outstanding; mode_of_payment 'Zelle' requires transaction_number. confirm=true.
Inputs: sales_invoice (required), mode_of_payment (required), amount, confirm, +1 more.
retry_consignment_invoiceA
Retry the settlement Sales Invoice for a Sold consignment line whose automatic booking failed (error log / total mismatch / dealer FFL expired at submit) — the Dealer Orders queue's Retry Invoice action. line = the Consignment Out Line name (see consignment_dealer_orders). To UNDO a booked settlement, cancel that settlement Sales Invoice instead (frappe_cancel_document) — the cancel hook reopens the parent. confirm=true.
Inputs: line (required), confirm.
return_consignment_linesA
Take unsold consigned guns BACK from the dealer: books a real FFL re-acquisition per gun (pushed to FastBound by its own hook) and moves stock back to the main (or given) warehouse. lines = Consignment Out Line names. confirm=true.
Inputs: lines (required), consignment_out (required), confirm, to_warehouse.
reverify_all_fflsC
Re-run ATF EZ Check for EVERY FFL supplier on file (batch re-verification). confirm=true.
Inputs: confirm.
rsr_catalog_searchA
Search the RSR distributor CATALOG (not local stock) by keyword / RSR stock # / UPC / MFG #. Use find_item for items already in this store. Read-only. Promote a catalog row to a sellable Item with promote_to_item.
Inputs: query (required), limit.
rsr_sync_catalogA
Trigger a full RSR catalog sync. Runs in the background — returns once queued.
Inputs: no named parameters.
rsr_test_connectionA
Probe the RSR FTPS connection + configuration (read-only).
Inputs: no named parameters.
sales_reportA
The POS Sales Report (revenue + profit, all channels), returned UNCHANGED — columns, rows and the report_summary cards. view: "Order" (per-order), "Order Detail" (order → line drill-down) or "Product" (default here — the CPA tax-reporting view: per-line tax from ERPNext's stored item-wise tax details, tax rows classified to Sales Tax / Shipping / Unclassified fail-closed). channel: POS | Web | Manual (empty = all). product_type: "Firearms" | "Non-Firearms" (empty = all) — any OTHER value is REFUSED by the server rather than ignored (F-2: an unrecognised value used to return everything unfiltered, which reads as "no rows of that type"). Profit is net (pre-tax). Read-only.
Inputs: to_date (required), from_date (required), view, channel, +1 more.
set_serial_titleA
Set the per-gun WooCommerce listing title for one firearm (writes Serial No.item_name). A per-serial firearm's Woo product name is taken from Serial No.item_name, falling back to the shared Item name — so this gives one physical gun its own title instead of the model name shared by every serial. Not yet live: it takes effect on the next push (woo_push_serial / woo_push_item). The field is fetch_if_empty so the value persists once set.
Inputs: title (required), serial_no (required).
set_stockA
Set a NON-serialized item's on-hand qty to an absolute value via Stock Reconciliation (physical count / write-off). reason is recorded in the audit trail. confirm=true.
Inputs: new_qty (required), item_code (required), reason, confirm, +1 more.
ship_consignment_outA
Dispose (ship) every Draft line of a Consignment Out: books the FFL transfer disposition per gun, moves stock to the consignment warehouse, pushes FastBound. All lines are validated before any is mutated; re-running ships 0 (idempotent). Server blocks on an invalid dealer FFL. VERIFY the dealer + serials first. confirm=true.
Inputs: consignment_out (required), confirm.
shipstation_test_connectionA
Probe the ShipStation API (GET /v2/carriers; read-only).
Inputs: no named parameters.
start_4473B
Kick off the ATF 4473 for a firearm POS/Sales Invoice: holds the invoice in Draft and opens a FastBound 4473 for the cashier to complete. serial_by_item_row maps invoice ITEM ROW name -> actual serial, e.g. {"a1b2c3d4": "SN123"}. Returns the FastBound URL. confirm=true.
Inputs: invoice_name (required), serial_by_item_row (required), confirm.
start_transfer_4473A
Start a customer-transfer 4473: builds the transfer POS Invoice server-side ($0 line per gun + one Transfer Fee line) and opens the FastBound 4473. Get fee_item and the default fee via frappe_run_method 'ffl_core.firearm.get_transfer_config'. FFL-dealer customers are refused server-side (their 4473 gate doesn't apply). confirm=true.
Inputs: serials (required), customer (required), fee_item (required), fee_amount (required), +3 more.
tax_liabilityA
Sales-tax liability roll-forward for the period, straight from the GL. TRAP: an invoice's "Total Taxes and Charges" includes shipping and other charges — it is NOT the sales tax; only GL rows on the sales-tax account(s) count, which is all this tool reads.
Accounts are resolved dynamically — the default enabled Sales Taxes and Charges Template's distinct account_heads (the same rule the server's setup/tax.py and Sales Report use; nothing hardcoded) — and echoed back in the result. Rows are is_cancelled=0; cancel+amend restatements land on the amended invoice's posting_date.
Output: opening_balance (before from_date), collected credits split by voucher (Sales Invoice = sales accrual; Journal Entry = adjustments, listed separately, never merged into sales), remitted debits split by voucher (Journal Entry = remittance payments; Sales Invoice = return / amend reversals), any other voucher type fail-closed into its own "other" bucket, and closing_balance. The identity closing == opening + credits − debits is asserted internally to the cent; a classification that drops rows raises instead of returning numbers. Single-state (TX) system — there is deliberately no state parameter. Read-only.
Inputs: to_date (required), from_date (required), company.
toggle_service_needB
Set a firearm's 'Service Needed' (gunsmith) flag on its Serial No, opening or closing the gunsmith ToDo accordingly. confirm=true.
Inputs: value (required), serial (required), confirm.
update_consignment_pricesA
Edit the per-consignment price snapshot on At Dealer lines — the queue's "Edit prices" dialog (PR #224). prices: {line_name: {"cost": required > 0, "msrp": tri-state}} — omit the "msrp" key to keep the stored MSRP, send "" / null to clear it, anything else must be > 0. Writes ONLY this shipment's line snapshot ("Dealer Price" + MSRP), never Serial No / Item masters; settlement and the dealer portal read the same snapshot, so both follow automatically. Server gates: line must be At Dealer with no booked settlement invoice; the whole batch is validated before anything is written (one bad line rejects all). Line names come from consignment_queue. confirm=true.
Inputs: prices (required), consignment_out (required), confirm.
update_settingsA
Update an integration's Settings. which: ffl | fastbound | rsr | payroc | woocommerce | dealer (dealer-portal WooCommerce) | shipstation | gunbroker. Credential/password fields are stripped — set those in Desk. On gunbroker the environment, credential and money fields are refused outright (enabled, sandbox_mode, base_url_override, dev_key, sandbox_dev_key, username, password, end_strategy, check_deposit_account, card_checkout_enabled) — the call fails rather than half-applying.
Inputs: which (required), values (required).
upload_attachmentA
Upload a LOCAL file to the POS as a File document, optionally attached to a document (doctype + name) and/or set into its Attach field (fieldname). Returns the File doc incl. file_url. Default is_private=true — product photos that WooCommerce must sideload need is_private=false. For bulk photo+gallery imports prefer the firearm-listing-import script (it resizes images first; oversized originals break the Woo push).
Inputs: file_path (required), name, doctype, fieldname, +1 more.
verify_supplier_fflB
Live ATF EZ Check on a Supplier's FFL and update that supplier's verification status/fields. confirm=true (it writes to the Supplier).
Inputs: supplier_name (required), confirm.
woo_delist_itemA
Set the WooCommerce product for an Item to Draft + stock 0, hiding it from the shop immediately. site: retail | dealer. confirm=true.
Inputs: item_code (required), site, confirm.
woo_delist_serialA
Set ONE Serial No's WooCommerce product to Draft + stock 0, hiding that gun from the shop immediately. site: retail | dealer. confirm=true.
Inputs: serial_no (required), site, confirm.
woo_push_itemA
Push (create or update) the WooCommerce product(s) for an Item. Per-serial firearms push every Active Serial No individually; non-firearm and uniform-price firearms push a single item-level product. For ONE gun use woo_push_serial instead. site: retail | dealer. Consequential — confirm=true.
Inputs: item_code (required), site, confirm.
woo_push_serialA
Push (create or update) the WooCommerce product for ONE firearm Serial No — the per-gun listing action (SKU item_code::serial). Prefer this over woo_push_item when only specific guns changed: woo_push_item pushes EVERY Active serial of that item. site: retail | dealer. confirm=true.
Inputs: serial_no (required), site, confirm.
woo_reconcileA
Push the item-level product AND all Active Serial Nos for an Item in one call. Suitable for the initial listing of a per-serial firearm where everything needs to go live at once. site: retail | dealer. confirm=true.
Inputs: item_code (required), site, confirm.
woo_test_connectionA
Probe a WooCommerce store's API connection (read-only). site: retail (main store) | dealer (dealer portal).
Inputs: site.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 77 tools
Most tools target clearly separated resources and workflows, and descriptions explicitly disambiguate near-twins like woo_push_item vs woo_push_serial and mark_shipped_manually vs mark_consignment_shipped. The sheer number of tools creates some selection noise, but an agent can usually determine the correct tool from the name and description.
Names are consistently snake_case and often use domain prefixes like woo_, frappe_, consignment_, and distributor_, which is helpful. However, the set mixes verb_noun actions (dispose_order, create_consignment_out), noun-phrase queue names (pending_orders, consignment_queue), and service-style names (test_connection, sync_catalog), so there is no single predictable pattern.
At 77 tools, this is far beyond the typical well-scoped MCP surface and exceeds the 25+ too-many threshold. While the domain is broad, packing generic CRUD wrappers, integration actions, queues, and reports into one server creates a heavy surface that will strain agent context and tool-selection accuracy.
The tool set covers nearly the full gun-store administration lifecycle: inventory, FFL/FastBound compliance, orders, consignments, distributor operations, e-commerce integrations, and financial reporting. A few workflow actions are only reachable via generic frappe_run_method or are missing dedicated tools—e.g., GunBroker push/end and explicit distributor-order confirmation—but these are minor gaps.
Maintenance
Related MCP Connectors
MCP server unifying ERPs, CRMs, APIs and knowledge base for Claude, ChatGPT and Gemini.
MCP server that lets AI assistants use all OneSchema features exposed via the public API.
Hosted MCP server connecting AI assistants to 9,000+ apps and 40,000+ actions via Zapier.
API-first CRM for LLMs - contacts, companies, deals and activities over a native MCP server.
Related MCP Servers
- FlicenseNot gradedqualityNot gradedmaintenanceAn MCP server that enables LLM agents to interact with PyerP ERP systems via a REST API. It allows users to search, read, create, and update ERP records such as inventory, clients, and users using natural language.-
- AlicenseAqualityDmaintenanceAI-ready MCP server that gives assistants real-time access to the Gunsnation firearms catalog.233 npm3MIT

Frappe MCPofficial
AlicenseNot gradedqualityFmaintenanceAllows Frappe Framework apps to function as MCP servers, exposing Python-defined tools for LLM interaction.170MIT- AlicenseNot gradedqualityAmaintenanceMCP server that enables LLMs to interact with ERPNext/Frappe sites for document CRUD, search, reports, workflows, and analytics, respecting user permissions and logging all actions.337AGPL 3.0