Skip to main content
Glama
genvjacobc

lightspeed-x

by genvjacobc

lightspeed-x-mcp

A Model Context Protocol server for Lightspeed X (Lightspeed Retail POS, the platform formerly known as Vend). It gives Claude, or any MCP client, read-only access to your store's sales, inventory, products, and customers, and it does the aggregation for you: revenue, units, COGS, gross profit, margin, discount, average basket value, and basket size, grouped by whatever dimension you ask for.

Read-only by construction. Every tool issues GET requests. There is no code path in this server that can create, update, or delete anything in your account, so you can point it at a live retail business without worrying about it.

"What sold best yesterday?"                → lightspeed_sales_report
"Revenue by store last week"               → lightspeed_sales_report, group_by: outlet
"Which SKUs need reordering?"              → lightspeed_inventory_report, status: reorder_needed
"What are our busiest hours?"              → lightspeed_sales_report, group_by: hour
"Margin by brand this month"               → lightspeed_sales_report, group_by: brand
"Pull up invoice 162220"                   → lightspeed_list_sales

Why this exists

The Lightspeed X API is a survivor of the Vend era and it has some sharp edges that make naive clients either slow or quietly wrong. This server handles them so the model does not have to:

Reality

What this server does

/sales accepts date_from and date_to, then silently ignores them. Every result comes back regardless of the dates you asked for.

Locates a date range by binary-searching the version sequence, then filters locally. A naive client that trusts the parameters returns wrong answers with total confidence.

Pagination is version based, not cursor based. There is no cursor key; responses carry version: {min, max} and you page with ?after=<version>.

Handled transparently by the client's paginate helper.

The documented max page_size is 200, but the API actually serves up to 5000.

Bulk scans use 5000 (sales use 1000, because sales carry their full line items). A 126,000-row inventory scan takes 26 requests instead of 630.

Sale line items carry only product.id. No name, no SKU, no category.

Joins against a cached product catalog so every report is human readable.

A store's day does not start at UTC midnight, so a naive split files evening sales on the wrong day.

Day, month, weekday, and hour buckets are resolved in the outlet's own IANA timezone.

Returns are booked as line items with negative quantity and negative totals.

They net out of every metric correctly, with no special casing needed.

/product_categories returns a completely different envelope shape from every other endpoint.

Handled as its own case. It does return the complete taxonomy, so there is no need to rebuild the tree from products.

On /products, two field names are the opposite of what they look like. product_category is the real category; categories is the tag collection (its ids match tag_ids), and there is no tags key at all.

Reads product_category for the category and categories for tags. Getting this backwards does not error, it silently groups sales by tag names like THCA under a column labelled Category, so scripts/check-catalog.mjs pins the behaviour.

category_id and product_category_id on /products are accepted, then silently ignored, returning the unfiltered page.

Category filtering runs client side against the cached catalog, same as the date handling on /sales.

Rate limit is 300 x registers + 50 per 5 minutes, and 429s carry no reliable Retry-After.

Exponential backoff with retries on 429 and 5xx.

How the revenue math is derived

Verified against 200 consecutive live sales. Every one reconciled to the sale's own totals.price within two cents:

line revenue excl tax = line_items[].pricing.total        (net of discount, already x quantity)
line COGS             = line_items[].pricing.cost_total
line discount given   = line_items[].pricing.discount_total
line tax              = line_items[].tax.total

Three further checks on live data, all exact:

  • Sum of per-day revenue over a week equals the week's grand total.

  • An outlet's row in a group_by: outlet report equals the same report re-run with that outlet's server-side filter.

  • The sum of amounts tendered by payment type equals revenue including tax.


Related MCP server: Toast MCP Server

Install

Three commands, no clone, no build, no paths to edit:

/plugin marketplace add genvjacobc/lightspeed-x-mcp
/plugin install lightspeed-x@lightspeed-x-mcp
/lightspeed-x:setup

The third command runs a bundled setup skill that walks you through getting a token, writes it to the right place, and verifies the connection against your live account before telling you it worked.

The plugin also ships a reports skill so Claude knows which tool answers which kind of retail question, and how to read the numbers it gets back.

Credentials live at ${CLAUDE_PLUGIN_DATA}/credentials.env, a per-user directory that survives plugin updates. Nothing is shared between machines or teammates.

Note that /plugin uninstall deletes that directory, so an uninstall and reinstall means running setup again. The token itself stays live in Lightspeed regardless, so revoke it there if you are done with it.

Option 2: as a standalone MCP server

git clone https://github.com/genvjacobc/lightspeed-x-mcp.git
cd lightspeed-x-mcp
npm install
npm run build

Requires Node 18 or newer.

Get an API token

In the Lightspeed X back office: Setup → Personal Tokens → Add Personal Token. Copy it before closing the dialog; it is shown once.

Two limits worth knowing before you plan a rollout:

  • Only admin users can create personal tokens, and Lightspeed gates the feature to Plus plans. If Personal Tokens does not appear under Setup, someone with admin access has to create the token for you.

  • Lightspeed does not offer read-only tokens. A token carries all permissions of the user who created it. This server only ever issues GET, but the token itself is a general-purpose credential, so treat it like a password and revoke it from the same screen if it leaks.

Check it works

npm run doctor

This validates your credentials, calls the live API, and names the exact cause of any failure. A wrong store domain and a bad token both return HTTP 401 from Lightspeed, so the doctor reports both possibilities rather than guessing.

Configure

Copy .env.example to .env and fill in your store:

LIGHTSPEED_DOMAIN=mystore
LIGHTSPEED_TOKEN=your_personal_token

LIGHTSPEED_DOMAIN accepts a bare prefix (mystore), a host (mystore.retail.lightspeed.app), or a full URL. All three resolve to the same place.

Multiple stores. Any LIGHTSPEED_<NAME>_DOMAIN + LIGHTSPEED_<NAME>_TOKEN pair defines an account named <name>, lowercased. Tools then take an optional account argument:

LIGHTSPEED_NORTH_DOMAIN=northstore
LIGHTSPEED_NORTH_TOKEN=token_for_north
LIGHTSPEED_SOUTH_DOMAIN=southstore
LIGHTSPEED_SOUTH_TOKEN=token_for_south
LIGHTSPEED_DEFAULT_ACCOUNT=north

Values already present in the environment always beat the .env file, so a host that injects credentials directly takes precedence.

Register with Claude Code (standalone path only)

Skip this if you installed the plugin; the plugin registers the server itself.

claude mcp add lightspeed-x -s user -- node /absolute/path/to/lightspeed-x-mcp/dist/index.js

Or add it to your config by hand:

{
  "mcpServers": {
    "lightspeed-x": {
      "command": "node",
      "args": ["/absolute/path/to/lightspeed-x-mcp/dist/index.js"],
      "env": {
        "LIGHTSPEED_DOMAIN": "mystore",
        "LIGHTSPEED_TOKEN": "your_personal_token"
      }
    }
  }
}

For Claude Desktop, the same block goes in claude_desktop_config.json.

Verify it locally with the MCP Inspector:

npm run inspect

Tools

lightspeed_sales_report

The main event. Aggregates a date range and groups it.

Argument

Notes

date_from, date_to

YYYY-MM-DD, inclusive, read in the reporting timezone

group_by

product (default), sku, category, brand, supplier, tag, outlet, register, salesperson, customer, day, month, weekday, hour, payment_type, none

metrics

revenue, revenue_incl_tax, units, sale_count, cogs, gross_profit, margin_pct, discount, tax, basket_value, basket_size, customer_count

sort_by, sort_direction, limit

Ranking controls

outlet_id

Applied server side, so it is genuinely fast

states

Defaults to closed, which is what a report means

timezone

IANA zone override for day boundaries

| Outlet          |   Revenue | Units | Sales | Basket value | Gross profit | Margin |
| --------------- | --------: | ----: | ----: | -----------: | -----------: | -----: |
| South Lincoln   | $3,401.60 |   193 |    91 |       $37.38 |    $2,342.35 |  68.9% |
| York            | $3,222.86 | 159.2 |    73 |       $44.15 |    $2,238.77 |  69.5% |

lightspeed_list_sales

Individual transactions, newest first, with optional line-item expansion. For drilling into one receipt, auditing a total, or looking at returns. Filters on outlet_id, customer_id, and min_total.

lightspeed_inventory_report

Stock on hand joined to product and outlet names, with retail and cost value of what is on the shelf.

status is the argument that matters:

Status

Meaning

low_stock

Still sellable but at or below the reorder point. What is running low.

reorder_needed

At or below the reorder point including zero and negative. The full buy list.

out_of_stock

Exactly zero.

negative

Below zero, which means a stock count error.

in_stock / all

More than zero / everything.

group_by rolls up to product, outlet, category, brand, or supplier, which is how you answer "how much inventory value sits in each category".

lightspeed_search_products

Free-text search over name, variant name, SKU, and handle, with brand / supplier / category / tag filters. Matching runs locally against the cached catalog because the API's own search endpoint ranks poorly, so results are exact substring matches.

lightspeed_get_product

Full detail for one product by ID or exact SKU, including per-outlet stock and computed margin.

lightspeed_search_customers / lightspeed_get_customer

Look up customers by email (pushed to the API), or by name, phone, or customer code (matched locally). Returns the UUID the sales tools take as a customer_id filter. This returns personal data; handle it accordingly.

lightspeed_list_outlets / lightspeed_list_registers / lightspeed_list_accounts

Resolve store names to the outlet UUIDs the report filters take, list POS lanes including e-commerce registers, and see which accounts the server can reach. lightspeed_list_accounts never returns tokens.

lightspeed_list_reference_data

One tool over brands, suppliers, product_categories, tags, customer_groups, payment_types, promotions, taxes, and users. Use it to get the exact spelling of a brand or category before filtering a report on it.

lightspeed_api_get

Escape hatch for any endpoint without a purpose-built tool: /consignments, /price_books, /serial_numbers, and so on. Only GET is ever issued.


Performance and limits

Reports are shaped by the fact that sales cannot be filtered by date server side.

Query

Typical cold time

One day, all outlets (~900 sales)

15 to 20s first call, then ~2s

One week (~5,900 sales)

~20s

Full inventory scan (~126,000 rows)

~25s first call, then instant

Product / outlet / reference lookups

Under 1s after the first call

Most of a cold call is the ~30 single-row probes that locate the date range. Those probes are remembered per account, so the second report in a session usually needs none. Catalog, outlet, register, user, and inventory scans are cached for 15 minutes.

To keep things fast: pass outlet_id when you only care about one store, and prefer narrow date ranges. LIGHTSPEED_MAX_SALES (default 200,000) caps a single call, and the tool tells you plainly when it truncates rather than quietly returning a partial answer.

One honest caveat. Because the date range is found by version, a sale created before the range but edited after it can be missed. LIGHTSPEED_SEEK_MARGIN_DAYS (default 1) sets how far before the range the search aims, and raising it widens the safety net at the cost of scanning more records. This is inherent to an API that will not filter by date, not a shortcut taken here.


Configuration reference

Variable

Default

Purpose

LIGHTSPEED_DOMAIN

required

Store prefix, host, or URL

LIGHTSPEED_TOKEN

required

Personal token

LIGHTSPEED_<NAME>_DOMAIN / _TOKEN

optional

Additional named accounts

LIGHTSPEED_DEFAULT_ACCOUNT

first account

Account used when a tool omits account

LIGHTSPEED_API_VERSION

2026-01

API version path segment

LIGHTSPEED_MAX_SALES

200000

Safety cap per sales call

LIGHTSPEED_SEEK_MARGIN_DAYS

1

Days of margin when seeking the version anchor

LIGHTSPEED_ENV_FILE

optional

Explicit path to a credential file. The plugin sets this to ${CLAUDE_PLUGIN_DATA}/credentials.env


Development

npm run dev      # run from source with tsx
npm run build    # compile to dist/
npm run inspect  # MCP Inspector against the built server
npm run doctor   # credentials + live connectivity check
npm run validate-plugin  # validate the plugin manifests
src/
  index.ts            entry point, env loading, tool registration
  config.ts           account discovery from the environment
  lib/
    client.ts         HTTP client, retry, version pagination
    version-seek.ts   date to version binary search
    sales.ts          sale fetching and metric aggregation
    catalog.ts        cached product, outlet, register, inventory lookups
    time.ts           timezone-aware day boundaries
    format.ts         Markdown table rendering, tool results
  tools/              one file per tool group

Tools return Markdown tables rather than raw JSON: a model reads an aligned table more reliably than a deep JSON blob, at a fraction of the tokens. The underlying numbers are also on structuredContent for programmatic callers.

Two conventions worth keeping if you contribute:

  • Never throw from a tool handler. Failures are returned as isError results. The guard() wrapper enforces this.

  • Never console.log. Stdout carries JSON-RPC frames. Diagnostics go to console.error.


Repository layout

.claude-plugin/     plugin + marketplace manifests
.mcp.json           MCP server declaration used by the plugin path
skills/setup/       guided connection walkthrough
skills/reports/     how to answer retail questions with these tools
src/                TypeScript source
dist/               compiled output, committed so plugin installs need no build

dist/ is intentionally tracked in git, because Claude Code plugin installation does not run a build step and the compiled server has to ship with the repo. Run npm run build before committing a source change, and bump version in package.json, .claude-plugin/plugin.json, and .claude-plugin/marketplace.json together when releasing.

License

MIT. See LICENSE.

Not affiliated with or endorsed by Lightspeed Commerce.

Available Tools

1 tool
lightspeed_setup_statusLightspeed setup statusA
Read-only

Report whether this server has working Lightspeed credentials, and if not, the exact file path to create and what to put in it. Call this first when any Lightspeed tool fails with a credential error, when the user says Lightspeed is not connected, or when setting the server up for the first time. Safe to call at any time and never returns the token itself.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already mark readOnlyHint=true and openWorldHint=true, so the description need not restate safety labels. It adds valuable context: the tool never returns the token itself, and it reports the exact file path and content to create when credentials are absent. This is a useful disclosure beyond the structured hints, warranting a 4.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences, front-loaded with the core purpose, then usage conditions, then a safety note. Every word earns its place; there is no fluff or repeated information. Ideal conciseness for a tool of this simplicity.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The tool is simple (no parameters, no output schema) and the description covers everything an agent needs to call it correctly: the exact condition to invoke it, what it reveals (success or failure plus remediation info), and its non-sensitive nature. Nothing is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With zero parameters, the schema is trivially covered at 100%, and the rubric sets a baseline of 4 for this case. The description adds no parameter-specific meaning, but nothing is missing—there is nothing to document. The baseline score is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a precise statement of what the tool does: report whether the server has working Lightspeed credentials and, if not, provide the exact file path and content to fix it. This is a specific verb-resource pair that leaves no doubt about the tool's role, and it stands alone clearly even without siblings.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives explicit, actionable usage guidance: call this first when a Lightspeed tool fails with a credential error, when the user reports Lightspeed is not connected, or during initial setup. It also notes the tool is safe to call at any time, covering both when to use and the absence of constraints.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 1 tool updatev1.2.0
    • First observedlightspeed_setup_status

TDQS

A4.2/5.0

Scored across 1 tool

Disambiguation5/5

With only one tool, there is no possibility of confusion or overlap between tool purposes. The tool's role as a credential/setup status check is clearly defined.

Naming Consistency5/5

A single tool name cannot exhibit inconsistency. 'lightspeed_setup_status' is descriptive, snake_case, and follows a predictable pattern for this server.

Tool Count1/5

A single setup/status tool is an extreme mismatch for a server named 'lightspeed-x'. The tool explicitly references other Lightspeed tools that do not exist, making the surface feel incomplete and unbalanced.

Completeness1/5

The server provides no actual Lightspeed operations—only a credential status check. This is severely incomplete for a Lightspeed integration domain and leaves agents with no way to perform real tasks.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    D
    maintenance
    Provides AI assistants with real-time access to Shopify store analytics, sales data, and inventory through ShopifyQL and the Admin GraphQL API. It enables users to query store performance, customer metrics, and marketing insights using natural language.
    13
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables AI assistants to query live Toast POS data and generate sales, labor, and cash reports while answering restaurant operations questions, all in a read-only manner.
    3
    MIT
  • F
    license
    C
    quality
    C
    maintenance
    Enables Claude or any MCP client to query a Toast POS using plain English, with 55 read-only tools covering orders, sales, labor, employees, menus, inventory, customers, and cash.
    55
    -
  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables AI assistants to query a retail and food-service point-of-sale database through predefined business tools for sales summaries, top products, margins, stagnant inventory, cash reconciliation, and optional stock adjustments, returning formatted markdown answers.
    -