sapo-mcp
Model Context Protocol server for Sapo.vn POS & e-commerce platform, providing 105 tools across 4 modes for managing orders, inventory, customers, and more.
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 --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
| 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.
<details>
<summary><b>pos-online — 51 tools (with SAPO_ALLOW_OPS=*)</b></summary>
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)
</details>
<details> <summary><b>web — 31 tools</b></summary>
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
</details>
<details> <summary><b>pos-counter — 15 tools</b></summary>
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)
</details>
<details> <summary><b>analytics — 10 tools</b></summary>
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
</details>
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) 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: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-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
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.