Skip to main content
Glama
GunStore-POS Admin MCP

GunStore-POS Admin MCP

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 deps

4. 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 = 120

Verify 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-mcp
  • Process env beats .env: the server loads .env with override=False, so env vars set in the registration win. A standalone install needs no .env file at all — and the same checkout can serve several entries with different FRAPPE_BASE_URL / mode combinations (e.g. a dev-site instance).

  • Verify: after connecting, tools/list must show exactly 30 tools and the server name gunstore-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

frappe_list_documents

list any doctype (filters / fields / limit; limit=0 = all)

frappe_get_document

fetch one doc (Single: name == doctype)

frappe_describe_doctype

fields incl. custom fields; flags password fields

frappe_create_document

create (credential fields stripped)

frappe_update_document

update (credential fields stripped)

frappe_delete_document

delete — needs confirm=true

frappe_submit_document

submit a draft — needs confirm=true

frappe_cancel_document

cancel a submitted doc — needs confirm=true

frappe_run_method

call any whitelisted method by dotted path (credential, signing, webhook and legacy-alias endpoints refused; consequential ones need confirm; GET-only endpoints are sent as GET)

list_pos_actions

search the POS endpoints the signed-in user can run: arguments, roles, whether confirm is needed, and the dedicated tool to prefer (needs a POS release with the action catalog)

frappe_run_report

run a Script/Query report

Curated

Tool

Purpose

get_settings / update_settings

ffl | fastbound | rsr | payroc | woocommerce | shipstation | gunbroker | sports_south | lipseys | data_service

find_item / item_stock / available_serials

typeahead item search / stock per item / in-stock serials + per-gun prices

firearms_in_stock

the Firearms In Stock report

receive_goods_preview

what receive_goods would do with a payload: the private-seller payout, unknown items, serials already on file (read-only)

distributor_incoming / distributor_receive / distributor_ensure_item

parcels on their way (read-only) / receive one against its Distributor Order or store invoice (booking needs confirm) / find or create the Item for a distributor SKU (confirm)

receive_goods

Purchase Receipt + FFL acquisitions + FastBound push (confirm); a paid purchase from a private seller (acquisition_source "Individual", type blank / Purchase / Individual) needs seller_payment_method (Cash / Zelle / Check / ACH) + seller_payment_reference unless Cash (gunstore-pos #705 on)

add_stock / set_stock

non-serialized stock add / absolute set (confirm)

toggle_service_need

gunsmith flag on a Serial No (confirm)

rsr_catalog_search

RSR-only catalog search (distributor_catalog_search spans every enabled house)

promote_to_item / backfill_from_rsr

RSR catalog row → sellable Item / backfill Item fields (confirm)

fastbound_test_connection / boundbook_reconcile

FB probe / bound-book reconcile (apply needs confirm)

boundbook_mismatches

dry-run-only bound-book reconcile (read-only; also in cpa mode)

atf_verify_ffl / verify_supplier_ffl / reverify_all_ffls

ATF eZ-Check verifies (confirm)

woo_test_connection / woo_push_item / woo_delist_item / woo_reconcile

store probe / list / delist / reconcile an Item — all take site: retail (the only store; writes need confirm)

woo_push_serial / woo_delist_serial

list / delist ONE gun (SKU item_code::serial; site; confirm)

set_serial_title

per-gun Woo listing title (writes Serial No.item_name; takes effect on next push)

GunBroker channel

the environment, credential and money fields of GunBroker Settings are refused on every write path — update_settings, frappe_update_document, frappe_create_document and frappe_run_method's field setters (method names matching set_value/db_set/save_doc-alike, safety._SETTER_METHOD), enforced by a test over the whole registered surface — every tool module the server registers. Sandbox vs live is a Desk change, not a tool call. The 4 listing writes below need GUNSTORE_MCP_GUNBROKER_ACTIONS=1 to be registered at all

gb_test_connection / gb_listing_status

GunBroker probe — the reply's sandbox flag says which marketplace answered (needs SYSTEM_ROLES, as does gb_pull_orders; the other three need STOCK_ROLES) / POS-vs-GunBroker view of ONE gun's listing (read-only)

gb_push_serial / gb_end_listing

list ONE gun as a fixed-price Buy Now / end its listing (opt-in via GUNSTORE_MCP_GUNBROKER_ACTIONS=1, then confirm; both reach GunBroker only via the POS)

gb_update_listing

reprice / replace photos / sync a model's quantity (0 ends it) / lift the "may already be sold" flag on a live listing (opt-in + confirm)

gb_pending_orders / gb_record_payment / gb_order_action / gb_dispose_order

open GunBroker orders (read-only; also cpa) / record the banked payment / assign units, refunds, shipment and tracking push, invoice retry / book the dispositions (confirm). Not behind the switch: they fulfil an order already sold and never change a listing offer

gb_pull_orders

run the order poll now (opt-in + confirm; takes no arguments). Importing an order creates POS documents and reserves the gun, which is why it is gated with the writes. The reply {"queued": true} is a receipt — the poll runs in the background and reports no counts

pending_orders / pending_web_orders

the Pending Order queue: counter/dealer rows + paid web orders (read-only; consignments live in consignment_queue)

dispose_order / dispose_web_order

book the FFL transfer dispositions — stock-out + FastBound push (confirm)

record_payment

Payment Entry against an unpaid submitted invoice; Zelle needs transaction_number (confirm)

card_charges

Card charges on a Sales Invoice that can still be voided or refunded (read-only)

void_card_charge

Void a whole card charge before it settles; the invoice owes it again (confirm)

refund_card_charge

Refund part or all of a card charge: Return (credit note) or Collect again (confirm)

resolve_card_reversal

Book or release a void/refund the processor never confirmed, after checking its portal (confirm)

cancel_order

cancel a submitted counter/dealer order — cascades disposition/stock/FastBound/ShipStation reversal + refund (confirm + reason)

push_shipment / mark_shipped_manually / shipstation_test_connection

ShipStation label push (confirm) / no-push escape hatch (confirm) / probe

consignment_queue / consignment_dealers / consignment_serials / consignment_dealer_orders

outbound-consignment reads: At-Dealer queue / shippable FFL dealers / pickable serials / settlement queue

create_consignment_out / ship_consignment_out / push_consignment_shipment / mark_consignment_shipped

build / dispose+ship / ShipStation push / manual-ship w/ tracking (confirm)

retry_consignment_invoice / return_consignment_lines / cancel_consignment

settlement-invoice retry / take unsold guns back / cancel line-or-draft (confirm; cancel needs reason)

update_consignment_prices

change cost / MSRP on guns out on consignment (confirm)

start_4473 / manager_override_4473 / start_transfer_4473

4473 kickoff / stuck-sale manager override / customer transfer (confirm)

web_order_action / web_order_exceptions / woo_store_maintenance

web-order pickup, invoice retry, refund reconcile, antique retire, backstop, stuck reservations (confirm) / month-end web checks (read-only; also cpa) / store-wide attribute, category and dangling-ID sweeps (apply needs confirm)

card_processor_test_connection

why the card terminal will not charge: switch vs readiness, live probe, terminals (read-only)

upload_attachment

multipart file upload → File doc, optionally attached to a doctype+name / Attach field

Counter orders and register tickets (tools/orders.py)

Tool

Purpose

preview_order / create_order / update_order

check an order (serials, tag prices, costs, tax, price approval; read-only) / take a counter, phone or transfer order / edit a pending one (confirm; save_as_draft needs none)

pending_order_action

fulfil from stock, re-source a line, open the pickup ticket, resend the agreement (confirm)

pos_draft_orders / pos_ticket

tickets parked at the register (read-only) / open, tender, card start/poll/cancel, submit a ticket (confirm except poll_card)

card_terminal_charge / saved_cards / charge_saved_card

counter terminal charge on a Sales Invoice (confirm for start and cancel) / cards on file (read-only) / charge a card on file (confirm; managers)

trade_in_intake

buy a gun from a walk-in seller and pay them now (confirm)

Customers, gunsmith, memberships (tools/customers.py, tools/services.py)

Tool

Purpose

customer_overview / customer_find

standing, alerts, family, IDs, agreements (and what a cart still needs signed) / find by email, phone, ID or name (read-only)

customer_manage / agreement_request / agreement_manage

merge, override a ban, set status, add an alert / send an agreement by email, tablet or for paper / void or record a paper signature (confirm)

gunsmith_jobs / gunsmith_intake / gunsmith_job_action

the repair queue (read-only) / take a gun in / start, ready, notify, payment, pickup, cancel, refund, log in the bound book (confirm)

memberships / membership_action

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 (confirm)

CRM (tools/crm.py)

Tool

Purpose

crm_find / crm_deal

leads and deals by email, name, status, owner, or waiting for a reply / one deal with its notes, tasks and customer card (read-only)

crm_want

the want list: list (read), add to a deal's contact, close (confirm); a fitting arrival gives the owner a task

crm_lead_action / crm_note_task / crm_open_order

convert a lead, change a status or owner / add a note or task / save a deal as a Create Order draft (confirm)

Compliance repairs and store admin (tools/compliance.py)

Tool

Purpose

refresh_4473_status / resolve_4473

re-check a ticket's 4473 and say the next step / attest a paper 4473 (remote only), redo or abandon (confirm)

fastbound_serial_diff / fastbound_serial_fix / boundbook_record_fix

compare guns with the bound book (read-only) / match, apply the differences, push a correction (confirm) / retry, link or mark an A&D record FastBound never got (confirm)

verify_customer_ffl / ffl_directory_search

eZ Check a dealer customer's FFL (confirm) / search the FFL directory (read-only)

firearm_costs / correct_firearm

per-gun cost (read-only) / correct a cost or a mistyped serial (confirm + reason)

store_overview / receive_goods_preview

the morning briefing and needs-attention counts / check a receive before booking it (read-only)

photo_gallery / edit_photo_gallery

a gun's or model's gallery (read-only) / add, reorder, set primary, caption, remove (confirm for remove and private files)

extensions_status / manage_extension

installed extensions and updates (read-only) / refresh, enable, disable, install, two-step update (System Manager; confirm)

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

inventory_counts / inventory_count_state / inventory_count_variance

list stocktakes / one count's scans + expected rows / counted-vs-system variance (read-only; counts + variance are also on the cpa surface)

inventory_count_create / inventory_count_cancel

start (whole store or scoped) / cancel an open count (confirm)

inventory_count_scan / inventory_count_set_qty / inventory_count_toggle_serial / inventory_count_undo

record a serial or UPC scan / type a quantity / tick a gun by hand / remove an entry (confirm)

inventory_count_finalize

posts ONE Stock Reconciliation for the non-serialized differences, saves the firearm report (confirm; firearms never adjusted)

cash_drawer_today / cash_drawer_preview_close

the drawer page (expected cash + lines) / what a close WOULD do (read-only)

cash_drawer_closes / cash_drawer_entries / cash_drawer_log / cash_drawer_weekly

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)

cash_drawer_close_day

count the drawer, close POS shifts, book the over/short or first-count entry (confirm)

cash_drawer_record_entry

kind = deposit | from_bank | expense (confirm); an expense needs expense_account + memo; a receipt is optional (POS 1.8.2+), and when given must be a file the same POS user uploaded within a day — remote has no upload tool, so the user uploads it in the POS and passes the file_url

cash_drawer_undo / cash_drawer_record_payout

cancel an entry or the latest close / change the method of / re-book an old-way seller payment (first payments go through the receipt) (confirm; managers)

storage_map / storage_location / storage_where / storage_unassigned

the store map (zones_only = zone list) / one location's contents / where a serial or item is / unassigned and to-confirm lists (read-only)

storage_create_zone / storage_add_positions / storage_set_disabled

zone management (confirm; Stock Manager)

storage_scan_move / storage_undo_move / storage_confirm_taken

assign a gun / put units into a location / undo it / say where sold units came from (confirm)

Distributor direct-ship (tools/distributor.py)

Tool

Purpose

distributor_orders / distributor_route_queue / distributor_catalog_search / distributor_test_connection

Distributor Orders / the confirmation queue / local catalog typeahead / probe one house (read-only)

distributor_quote / distributor_check_availability / distributor_precheck_fds / distributor_fulfillment_options

cost, MAP and block flags / live stock re-check / will it ship to that dealer / per-line options for one web order (read-only)

distributor_create_draft_order

Draft restock order(s) shipped to the store (confirm)

distributor_confirm_order / distributor_cancel_order / distributor_reroute / distributor_update_order_ffl

the 4 queue actions: place / cancel / re-route / fix an FDS hold (opt-in via GUNSTORE_MCP_DISTRIBUTOR_ACTIONS=1, then confirm; never on the remote connector)

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

hr_staff / hr_attendance / hr_leave

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)

hr_hours_summary

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)

hr_leave_decide

approve or reject an Open Leave Application, a reject needs a reason (confirm)

hr_attendance_fix

action = add_checkin | mark_attendance: add a missed punch / mark and submit a day's Attendance, with a recorded reason (confirm)

hr_onboard

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 (confirm)

Range board (tools/range_board.py, opt-in via GUNSTORE_MCP_RANGE=1)

Tool

Purpose

range_board / range_free_lanes / range_find_bookings

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)

range_guests

who is on a booking, what each owes and who has paid, and the tickets Check out would open (read-only)

range_book / range_update / range_cancel

book a lane or block / change a booking / cancel with a reason (confirm)

CPA reports (read-only; registered in both modes)

Tool

Purpose

sales_report

the POS Sales Report, payload passed through unchanged (views Order / Order Detail / Product; channel POS/Web/Manual)

inventory_receipts

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_entries

GL rows for a date range (is_cancelled=0 always; explicit truncated:true)

financial_statement

P&L / Balance Sheet (Date Range) / Trial Balance (fiscal-year auto-resolved)

tax_liability

sales-tax liability roll-forward from the GL — accounts resolved from the default sales-tax template, vouchers bucketed fail-closed, cent-exact identity asserted

payroc_transactions

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 payroc/ledger.py, and an API user with System Manager / Accounts Manager / Accounts User

ar_ap_summary

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 API mcp tool 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.py and 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.

  • cpa mode keeps all three read-only layers. The full surface is 172 tools remotely: upload_attachment reads 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_order is not one of them and is there); the 4 GunBroker writes open on the full connector only, where the POS deploy sets GUNSTORE_MCP_GUNBROKER_ACTIONS from 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 sets MCP_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; optional GUNSTORE_MCP_HOST (listen address, default 127.0.0.1) and FRAPPE_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 /mcp and 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 (opengunstore deploy/prod/mcp, pinned MCP_TAG) — see deploy/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>/run with the method WordPress requires for that ability (WP_REST_Abilities_V1_Run_Controller::validate_request_method): GET with ?input[...]= for a read-only ability, DELETE with the same query input for one marked both destructive and idempotent, POST with a JSON body {"input": …} for any other. Nothing is retried.

  • Reads: two gates, both must pass, failing closed: an ability in WEB_ABILITIES or WEB_ABILITIES_RESTRICTED becomes a tool only if the site's metadata also says annotations.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 marked readOnlyHint; writes readOnlyHint: false, and destructiveHint when the name contains delete, update-status, change-campaign-status, send or bulk, 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-note also says that a note with customer_note: true is 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 default in its input schema on the site to be callable over GET. A POST write 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, manager or none). No role: every call is refused. Abilities in WEB_ABILITIES_RESTRICTED (e.g. contact-level data) and every write run only for manager; 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 as j***@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=http is required; stdio refuses to start): WEB_SITE_URL (https; plain http only for localhost), WEB_SITE_USER, WEB_SITE_APP_PASSWORD, and WEB_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 .env out 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 tools
add_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_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 a reason: 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) or stale_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_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

    B3.4/5.0

    Scored across 77 tools

    Disambiguation4/5

    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.

    Naming Consistency3/5

    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.

    Tool Count2/5

    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.

    Completeness4/5

    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

    ActivityActive
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • F
      license
      Not graded
      quality
      Not graded
      maintenance
      An 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.
      -
    • A
      license
      Not graded
      quality
      A
      maintenance
      MCP 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.
      337
      AGPL 3.0