sapo-mcp
v0.7.1
Published
Model Context Protocol server for Sapo.vn — POS & e-commerce API access for Claude & AI agents
Maintainers
Readme
sapo-mcp
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 requireconfirm: 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 --versionOption B — global install
npm install -g sapo-mcp
sapo-mcp --version
sapo-mcp --helpOption C — local project dependency
npm install sapo-mcp
npx sapo-mcp --mode=pos-onlineQuick 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-onlineOpen 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-counterexcludes 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 carrytruncated: truewhen 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-onlineSupported 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 canaryWrite-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=httpEndpoints:
| 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) requiresSAPO_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:localDevelopment
npm install
npm run typecheck # Type check
npm run lint # Lint
npm run test # Run tests
npm run build # Build for releasePost-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-counterand 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
