@curless/shopify-storefront
v0.54.0
Published
One Shopify storefront MCP connector, driven by a config file. Identity, tool names, vocabulary and shop wiring come from a StorefrontConfig, so a second merchant is a JSON file rather than a fork of 3,400 lines.
Readme
@curless/shopify-storefront
A Shopify store as an MCP connector for AI assistants (Claude, ChatGPT and any other MCP host). A buyer browses the catalogue, picks options, builds a cart and is handed a link to the merchant's own Shopify checkout — the money and the order stay in the merchant's Shopify. Results render as an MCP Apps card.
One server, driven by a config file: the merchant's name, tool names, the words
for what they sell and the shop wiring all come from config.json, so a second
merchant is a second file, not a fork.
npm install -g @curless/shopify-storefront
STOREFRONT_CONFIG=./config.json \
STOREFRONT_SHOPIFY_CLIENT_ID=… STOREFRONT_SHOPIFY_CLIENT_SECRET=… \
curless-shopify-storefrontDeployed on Node 22. It listens on PORT (default 8090) and expects a TLS-terminating
reverse proxy in front of it that sets X-Forwarded-For.
The tools
Named from the config's naming.prefix and nouns — with prefix acme and the
default noun product:
| Tool | What it does |
|---|---|
| list_acme_products | The catalogue as a card; optional query, maxPrice, limit. |
| get_acme_product | One product: options, prices, which combinations are in stock. |
| add_to_acme_cart / view_acme_cart / clear_acme_cart | A cart kept by this server, in memory, per conversation. Nothing reaches Shopify until the buyer pays. |
| get_acme_checkout_link | A Shopify cart permalink for one variant, or for the whole cart. Creates nothing. |
| lookup_acme_order | One order, given its order number and the email it was placed with. |
| get_acme_store_policies | The shop's published returns, shipping, privacy and terms pages. |
| check_acme_checkout_status | For the card only: whether the checkout it opened has been paid. |
Configuration
config.json holds everything that identifies the merchant; the environment
holds only secrets and machine settings.
{
"id": "acme",
"server": { "name": "acme-shop", "title": "Acme", "websiteUrl": "https://acme.example" },
"naming": { "prefix": "acme" },
"shopify": { "mode": "env", "shopDomain": "acme.myshopify.com" },
"urls": { "appUrl": "https://mcp.acme.example" },
"legal": {
"operatorContact": "[email protected]",
"merchantContact": "[email protected]",
"updated": "1 October 2026"
}
}shopify.mode: "env"serves one shop, installed once through the app's OAuth flow (/auth/install); the token is stored atshopify.tokenPath(default/app/data/<id>-token.json— keep it on a volume)."oauth-install"is the multi-shop form, where the shop is whoever installed the app and the connector URL is/s/<shop>/mcp.urls.appUrlis the app URL registered with Shopify;urls.publicUrl, when set, is the hostname buyers connect to.legal.*fills the/privacy,/termsand/docspages that directory reviewers read. Missing contacts do not fail the boot, but leave gaps there.
| env | |
|---|---|
| STOREFRONT_CONFIG | Path to config.json (required). |
| STOREFRONT_SHOPIFY_CLIENT_ID / _CLIENT_SECRET | The Shopify app's credentials; the secret also signs the OAuth callback and webhooks. |
| STOREFRONT_SHOPIFY_ADMIN_TOKEN | Optional: an existing Admin API token, skipping the install. |
| PORT | Default 8090. |
| CATALOG_REFRESH_MS | Default 300000 (5 minutes). A failed refresh keeps serving the last good catalogue. |
| OPENAI_APPS_CHALLENGE_TOKENS | host=token,… — ChatGPT Apps domain verification. |
Endpoints
POST /mcp (and /) is the MCP endpoint (streamable HTTP, JSON responses).
Alongside it: /healthz, /docs, /privacy, /terms, /icon.png,
/logo.png, /img (the card's image proxy for Shopify's CDN), and the
Shopify install routes /auth/install, /auth/callback, /webhooks/shopify.
What it guards
- No write access to the shop is needed for selling. Checkout is a Shopify link; no card number or payment token reaches this server.
- Order lookup needs the order number and the email together, answers a wrong email and a missing order alike, and pauses lookups after five misses in fifteen minutes for one order number, one email or one conversation (fifty for one network address, a hundred for the whole shop).
- Limits: 60 requests a minute per address, a 1 MB request body, bounded tool arguments.
- The merchant's own text (descriptions, policies) is stripped of control and invisible characters, length-capped and fenced before it reaches the assistant.
License
MIT
