npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

sapo-mcp

v0.7.1

Published

Model Context Protocol server for Sapo.vn — POS & e-commerce API access for Claude & AI agents

Readme

sapo-mcp

npm version License: MIT Node 20+

Model Context Protocol server for Sapo.vn POS & e-commerce platform.

Features

  • 4 modes, 105 unique tools: pos-online (51), web (31), pos-counter (15), analytics (10) — 2 read tools shared between pos-online and web
  • Two transports: stdio (Claude Desktop, Cursor) and Streamable HTTP (Docker, GoClaw)
  • Safe by default: destructive ops gated via SAPO_ALLOW_OPS (default: none); all destructive calls also require confirm: true
  • Single-tenant: one shop per server instance via Private App credentials
  • Pre-1.0: tool names and schemas may shift on minor bumps. See Post-1.0 roadmap.

Installation

Requires Node.js 20 or newer. Verify with node --version.

Option A — npx (no install)

Recommended for MCP clients (Claude Desktop, Cursor). Always pulls the latest published version:

npx -y sapo-mcp@latest --version

Option B — global install

npm install -g sapo-mcp
sapo-mcp --version
sapo-mcp --help

Option C — local project dependency

npm install sapo-mcp
npx sapo-mcp --mode=pos-online

Quick Start

1. Get Credentials

Create a Private App at developers.sapo.vn:

  • Store name: mystorenamemystorename.mysapo.net
  • API Key + Secret

2. Configure your MCP client

Claude Desktop

Edit ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%/Claude/claude_desktop_config.json (Windows):

{
  "mcpServers": {
    "sapo": {
      "command": "npx",
      "args": ["-y", "sapo-mcp", "--mode=pos-online,web,analytics"],
      "env": {
        "SAPO_STORE": "mystorename",
        "SAPO_API_KEY": "xxx",
        "SAPO_API_SECRET": "yyy"
      }
    }
  }
}

Restart Claude Desktop. Tools appear under the 🔌 plug icon.

Cursor

Edit ~/.cursor/mcp.json or per-workspace .cursor/mcp.json:

{
  "mcpServers": {
    "sapo": {
      "command": "npx",
      "args": ["-y", "sapo-mcp", "--mode=pos-online,web"],
      "env": {
        "SAPO_STORE": "mystorename",
        "SAPO_API_KEY": "xxx",
        "SAPO_API_SECRET": "yyy"
      }
    }
  }
}

MCP Inspector (test/debug)

npx @modelcontextprotocol/inspector \
  -e SAPO_STORE=mystorename \
  -e SAPO_API_KEY=xxx \
  -e SAPO_API_SECRET=yyy \
  npx -y sapo-mcp --mode=pos-online

Open the printed URL in a browser to inspect/invoke registered tools.

Use --mode=pos-online,web,analytics to register multiple modes (union of tools; shared tools registered once).

3. CLI Flags

| Flag | Default | Description | |------|---------|-------------| | --mode=<modes> | pos-online | Comma-separated list of modes to activate | | --transport=<t> | stdio | Transport type (stdio or http) | | --port=<port> | 3333 | HTTP port (ignored for stdio) | | --help | — | Print usage and exit | | --version | — | Print version and exit |

Environment Variables

| Variable | Required | Default | Description | |----------|----------|---------|-------------| | SAPO_STORE | Yes | — | Store subdomain | | SAPO_API_KEY | Yes | — | Private App API Key | | SAPO_API_SECRET | Yes* | — | Private App API Secret | | SAPO_API_SECRET_FILE | Yes* | — | Path to file containing secret (takes precedence) | | SAPO_ALLOW_OPS | No | "" | CSV of allowed destructive categories | | SAPO_MAX_AUTO_PAGES | No | 10 | Max auto-pagination pages | | SAPO_RETRY_MAX | No | 3 | HTTP retry attempts | | SAPO_LOG_LEVEL | No | info | Log level (error/warn/info/debug/trace) | | SAPO_HTTP_HOST | No† | 127.0.0.1 | HTTP bind host (loopback by default) | | SAPO_HTTP_PORT | No | 3333 | HTTP port | | SAPO_HTTP_MAX_SESSIONS | No | 100 | Max concurrent MCP sessions | | SAPO_HTTP_SESSION_IDLE_MS | No | 1800000 | Idle session GC threshold (30 min) | | SAPO_MCP_AUTH_TOKEN | No† | — | Bearer token. Required if host is non-loopback | | SAPO_HTTP_CORS_ORIGINS | No | — | CSV of allowed CORS origins (default: disabled) |

*One of SAPO_API_SECRET or SAPO_API_SECRET_FILE is required. †HTTP-only. Token is enforced when SAPO_HTTP_HOST is not 127.0.0.1/localhost/::1.

Modes

| Mode | Status | Description | |------|--------|-------------| | pos-online | 0.5.0 | Online orders, customers, fulfillment (51 tools) | | web | 0.5.0 | Storefront, collections, articles, SEO (31 tools) | | pos-counter | 0.5.0 | POS counter: locations, inventory write, suppliers, shifts, stock transfers (15 tools) | | analytics | 0.5.0 | Composed reports: revenue, top products/customers, LTV, tax, channel breakdown, discount usage, shift report (10 tools) |

Note: pos-counter excludes 5 internal-only endpoints (purchase_orders, purchase_returns, stock_adjustments, cash_transactions, cashbook) — these return HTTP 403 for Private App credentials and require an OAuth Partner App (post-1.0 roadmap).

Analytics tools auto-paginate up to SAPO_MAX_AUTO_PAGES; results carry truncated: true when the cap is hit.

Tool Verification Status

Verification levels for the 105 unique tools (last updated 2026-04-30):

| Symbol | Level | What it means | |---|---|---| | ✅ | Canary-monitored | Endpoint + schema verified live; daily nightly probe via .github/workflows/canary.yml | | 🟢 | Live-verified | Endpoint hit during development, schema captured to fixture, not in daily canary | | 🟡 | Docs-only | Schema from docs/sapo-api-reference.md (Sapo official); mock tests only, no live probe | | 🔵 | Composed | Not a Sapo endpoint — internal aggregation logic, logic-tested via unit tests | | 🚨 | Broken | Known non-functional, see notes |

Canary-monitored endpoints (drift detected within 24h)

| Resource | Mode(s) | Endpoint | |---|---|---| | store | all | /admin/store.json | | products (read) | pos-online, pos-counter | /admin/products.json | | orders | pos-online, pos-counter | /admin/orders.json | | customers | pos-online, pos-counter | /admin/customers.json | | inventory_levels | pos-online, pos-counter | /admin/inventory_levels.json | | locations | pos-counter | /admin/locations.json | | draft_orders | pos-online | /admin/draft_orders.json | | price_rules | pos-online | /admin/price_rules.json | | pages | web | /admin/pages.json | | suppliers | pos-counter | /admin/suppliers.json | | stock_transfers | pos-counter | /admin/stock_transfers.json | | payment_methods | pos-counter | /admin/payment_methods.json |

🚨 Broken — needs investigation

| Tool | Resource | Issue | |---|---|---| | list_pos_shifts | pos_shifts | /admin/pos_shifts.json returns Content-Type: text/html (Sapo POS web app shell), not JSON. | | get_pos_shift | pos_shifts | Same as above. JSON API endpoint not yet located. |

These 2 tools may fail at runtime. POS shift management currently requires admin UI.

Per-tool status

Click each section to expand the full tool list with status icons.

Customers (7)

  • list_customers, get_customer, search_customers, count_customers, list_customer_orders
  • 🟢 create_customer, update_customer

Customer Addresses (4)

  • 🟡 list_customer_addresses, add_customer_address, update_customer_address, set_default_customer_address

Products (read) (4)

  • list_products, get_product, search_products, count_products

Variants (read) (2)

  • 🟡 list_variants_for_product, get_variant

Inventory (read) (1)

  • get_inventory_levels

Orders (4)

  • list_orders, get_order, count_orders, search_orders

Order Transactions (2)

  • 🟢 list_order_transactions, create_order_transaction

Fulfillments (4)

  • 🟢 list_fulfillments_for_order, get_fulfillment
  • 🟡 create_fulfillment, update_fulfillment_tracking

Refunds (3)

  • 🟢 list_refunds, get_refund, create_refund (create_refund destructive: SAPO_ALLOW_OPS=refund)

Draft Orders (7)

  • list_draft_orders, get_draft_order
  • 🟡 create_draft_order, update_draft_order, complete_draft_order, send_draft_order_invoice, delete_draft_order (destructive: delete)

Price Rules (5)

  • list_price_rules, get_price_rule
  • 🟡 create_price_rule, update_price_rule, delete_price_rule (destructive: delete)

Discount Codes (3)

  • 🟡 list_discount_codes, create_discount_code, delete_discount_code (destructive: delete)

Destructive (cancel/delete-strict) (4)

  • 🟡 cancel_order, close_order, cancel_fulfillment (destructive: cancel)
  • 🟡 delete_customer, delete_variant (destructive: delete_strict)

Store (1)

  • get_store_info

Articles (5)

  • 🟡 list_articles, get_article, create_article, update_article, delete_article (destructive: delete)

Blogs (5)

  • 🟡 list_blogs, get_blog, create_blog, update_blog, delete_blog (destructive: delete)

Pages (4)

  • list_pages, get_page
  • 🟡 update_page_seo, delete_page (destructive: delete)

Collections (10)

  • 🟡 list_custom_collections, get_custom_collection, create_custom_collection, update_custom_collection, delete_custom_collection (destructive: delete)
  • 🟡 list_smart_collections, get_smart_collection
  • 🟡 list_collects, create_collect, delete_collect (destructive: delete)

Script Tags (3)

  • 🟡 list_script_tags, create_script_tag, delete_script_tag (destructive: delete)

Products SEO (1)

  • 🟡 update_product_seo

Variants (read, shared) (2)

  • 🟡 list_variants_for_product, get_variant

Locations (2)

  • list_locations, get_location

Payment Methods (1)

  • list_payment_methods

Inventory (write) (3)

  • 🟡 adjust_inventory_level, connect_inventory_level
  • 🟡 set_inventory_level (destructive: inventory_set)

Variants (write) (1)

  • 🟡 update_variant

POS Orders (2)

  • list_pos_orders, get_pos_order (uses /admin/orders?source_name=pos)

Suppliers (2)

  • list_suppliers, get_supplier

Stock Transfers (2)

  • list_stock_transfers, get_stock_transfer

POS Shifts (2)

  • 🚨 list_pos_shifts, get_pos_shift (see Broken section above — endpoint returns HTML)

All 10 are 🔵 composed (aggregate from multiple Sapo endpoints, no single endpoint to verify):

revenue_summary, top_products, top_customers, customer_ltv, tax_summary, online_vs_counter_breakdown, discount_usage_report, shift_report, inventory_low_stock, inventory_value

Destructive Operations

Destructive tools (cancel, delete, bulk-delete) are blocked by default. To enable specific categories:

# Allow order cancellation and standard deletes only
SAPO_ALLOW_OPS=cancel,delete npx sapo-mcp --mode=pos-online

Supported categories: cancel, delete, delete_strict, inventory_set, refund, shift_close, cashbook_write.

All destructive tool calls also require confirm: true in the tool arguments — this prevents accidental execution when an LLM calls the tool without explicit intent.

Security

ENV credentials are visible to other processes via ps / /proc/<pid>/environ on shared hosts. Use SAPO_API_SECRET_FILE to pass the secret via a file instead of an env var where possible.

Note: full secret-file isolation (reading the secret only at startup, then clearing) is tracked for the post-1.0 roadmap.

Probing

Verify which Sapo API endpoints are available on your store before configuring tools.

# Read-only probe — safe to run against any store (GET only)
SAPO_STORE=mystore SAPO_API_KEY=xxx SAPO_API_SECRET=yyy npm run probe

# Schema drift check — same probe + Zod validation against fixtures
npm run canary

Write-probe (npm run probe:write) refuses to run unless SAPO_STORE contains test, dev, or sandbox — never against production.

HTTP Transport (Remote / Docker)

Run as an HTTP server for remote MCP clients (e.g. GoClaw) or self-hosted Docker:

# Local-only (no auth required)
sapo-mcp --mode=pos-online --transport=http --port=3333

# Public bind (auth token required)
SAPO_HTTP_HOST=0.0.0.0 \
SAPO_MCP_AUTH_TOKEN=$(openssl rand -hex 32) \
sapo-mcp --mode=pos-online --transport=http

Endpoints:

| Method | Path | Description | |--------|------|-------------| | GET | /health | Liveness probe — returns { status, version, modes, sessions } | | POST | /mcp | JSON-RPC over Streamable HTTP (creates a session on initialize) | | GET | /mcp | SSE long-poll for an existing session (mcp-session-id header) | | DELETE | /mcp | Terminate a session |

Session model: Each MCP client gets a UUID session, isolated by an McpServer instance. Idle sessions (SAPO_HTTP_SESSION_IDLE_MS) are evicted automatically. Concurrent sessions cap at SAPO_HTTP_MAX_SESSIONS (503 returned when full).

Security:

  • Defaults to 127.0.0.1 — loopback only. Token optional.
  • Setting SAPO_HTTP_HOST=0.0.0.0 (or any non-loopback) requires SAPO_MCP_AUTH_TOKEN. The server refuses to start otherwise.
  • CORS is off by default. Enable per-origin via SAPO_HTTP_CORS_ORIGINS (CSV of origins, or * for all). Only enable when serving browser-based agents.

Docker

See examples/Dockerfile and examples/docker-compose.yml.

docker build -f examples/Dockerfile -t sapo-mcp:local .
docker run --rm -p 3333:3333 \
  -e SAPO_STORE=mystore \
  -e SAPO_API_KEY=xxx -e SAPO_API_SECRET=yyy \
  -e SAPO_HTTP_HOST=0.0.0.0 \
  -e SAPO_MCP_AUTH_TOKEN=$(openssl rand -hex 32) \
  sapo-mcp:local

Development

npm install
npm run typecheck   # Type check
npm run lint        # Lint
npm run test        # Run tests
npm run build       # Build for release

Post-1.0 roadmap

The 1.0.0 stable cut is gated on the following items, deferred from this release:

  • MCP Resourcessapo://shop/info, sapo://orders/today, sapo://orders/pending, sapo://inventory/low-stock. Read-heavy data is more efficient as a Resource than repeated tool calls.
  • MCP Prompts — templated workflows (respond_to_complaint, weekly_report, seo_optimize_product, customer_followup).
  • Webhook receiver — sub-package surfacing Sapo webhooks as MCP events.
  • OAuth 2.0 Partner App — multi-tenant SaaS deployments. Unlocks the 5 internal-only endpoints currently excluded from pos-counter and 4 deferred finance/PO reports (cashflow_summary, pnl_summary, supplier_purchase_summary, daily_pos_report).
  • Storefront GraphQL module — verify Private App access; scope tools if available.

Changelog

See CHANGELOG.md for the per-version history.

License

MIT — See LICENSE