Skip to content
Conduit

mcp server

Sapo-MCP

Model Context Protocol server for Sapo.vn POS & e-commerce — 105 tools across 4 modes, stdio + Streamable HTTP, single-tenant Private App.

nguyennguyenit/Sapo-MCPcommunitylistedunreviewednot running here yet

Set up

Runs on your computer. Pick your agent and operating system.

Before you start

  • Node.js 20 or newer
~/Library/Application Support/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "sapo-mcp": {
      "command": "npx",
      "args": [
        "-y",
        "sapo-mcp"
      ]
    }
  }
}

same format Cursor, GitHub Copilot CLI, Junie, JetBrains AI Assistant, Augment (Auggie CLI), Factory Droid, Kiro, Devin CLI +12

Values in angle brackets are placeholders. Restart Claude Desktop after editing its config.Claude Desktop docs

README

synced Oct 14, 2026

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: mystorename → mystorename.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

FlagDefaultDescription
--mode=<modes>pos-onlineComma-separated list of modes to activate
--transport=<t>stdioTransport type (stdio or http)
--port=<port>3333HTTP port (ignored for stdio)
--help—Print usage and exit
--version—Print version and exit

Environment Variables

VariableRequiredDefaultDescription
SAPO_STOREYes—Store subdomain
SAPO_API_KEYYes—Private App API Key
SAPO_API_SECRETYes*—Private App API Secret
SAPO_API_SECRET_FILEYes*—Path to file containing secret (takes precedence)
SAPO_ALLOW_OPSNo""CSV of allowed destructive categories
SAPO_MAX_AUTO_PAGESNo10Max auto-pagination pages
SAPO_RETRY_MAXNo3HTTP retry attempts
SAPO_LOG_LEVELNoinfoLog level (error/warn/info/debug/trace)
SAPO_HTTP_HOSTNo†127.0.0.1HTTP bind host (loopback by default)
SAPO_HTTP_PORTNo3333HTTP port
SAPO_HTTP_MAX_SESSIONSNo100Max concurrent MCP sessions
SAPO_HTTP_SESSION_IDLE_MSNo1800000Idle session GC threshold (30 min)
SAPO_MCP_AUTH_TOKENNo†—Bearer token. Required if host is non-loopback
SAPO_HTTP_CORS_ORIGINSNo—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

ModeStatusDescription
pos-online0.5.0Online orders, customers, fulfillment (51 tools)
web0.5.0Storefront, collections, articles, SEO (31 tools)
pos-counter0.5.0POS counter: locations, inventory write, suppliers, shifts, stock transfers (15 tools)
analytics0.5.0Composed 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):

SymbolLevelWhat it means
✅Canary-monitoredEndpoint + schema verified live; daily nightly probe via .github/workflows/canary.yml
🟢Live-verifiedEndpoint hit during development, schema captured to fixture, not in daily canary
🟡Docs-onlySchema from docs/sapo-api-reference.md (Sapo official); mock tests only, no live probe
🔵ComposedNot a Sapo endpoint — internal aggregation logic, logic-tested via unit tests
🚨BrokenKnown non-functional, see notes

Canary-monitored endpoints (drift detected within 24h)

ResourceMode(s)Endpoint
storeall/admin/store.json
products (read)pos-online, pos-counter/admin/products.json
orderspos-online, pos-counter/admin/orders.json
customerspos-online, pos-counter/admin/customers.json
inventory_levelspos-online, pos-counter/admin/inventory_levels.json
locationspos-counter/admin/locations.json
draft_orderspos-online/admin/draft_orders.json
price_rulespos-online/admin/price_rules.json
pagesweb/admin/pages.json
supplierspos-counter/admin/suppliers.json
stock_transferspos-counter/admin/stock_transfers.json
payment_methodspos-counter/admin/payment_methods.json

🚨 Broken — needs investigation

ToolResourceIssue
list_pos_shiftspos_shifts/admin/pos_shifts.json returns Content-Type: text/html (Sapo POS web app shell), not JSON.
get_pos_shiftpos_shiftsSame 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:

MethodPathDescription
GET/healthLiveness probe — returns { status, version, modes, sessions }
POST/mcpJSON-RPC over Streamable HTTP (creates a session on initialize)
GET/mcpSSE long-poll for an existing session (mcp-session-id header)
DELETE/mcpTerminate 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 Resources — sapo://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

↑↓ navigate openesc close