Shopify Mcp — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Shopify Mcp (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
A single MCP server exposing 100% of the Shopify Admin GraphQL API read surface (version 2026-04) through 6 universal tools. Read-only is enforced at the query-parser level — mutations are rejected before they ever reach Shopify, not merely discouraged. Multi-store by design: one server instance can serve many shops.
Built and maintained by Scalably. Runs on the Model Context Protocol. License: MIT.
Why read-only at the parser level? Giving an AI agent write access to a live store is how you end up with a deleted product or a wrong-priced variant. This server enforces read-only by parsing every query and rejecting mutations before they leave the process — not by trusting the model to behave, and not by relying on Shopify-side scopes alone. It's the safety boundary an agent in production actually needs. (more on the pattern)
pip install -r requirements.txt
# Single store (simplest)
export SHOPIFY_DOMAIN="my-store.myshopify.com"
export SHOPIFY_ACCESS_TOKEN="shpat_..." # or SHOPIFY_CLIENT_ID + SHOPIFY_CLIENT_SECRET
python server.pyThen point any MCP client at the server. See Authentication below for multi-store and OAuth client-credentials setup.
| Tool | Description |
|---|---|
shopify_list_stores | Lists configured stores. Agent calls first. |
shopify_graphql_query | Arbitrary read-only GraphQL. Mutations rejected by the parser. |
shopify_graphql_introspect | Schema introspection — full catalog or single type. |
shopify_bulk_query | Launch async bulk export (JSONL). |
shopify_bulk_poll | Poll bulk operation status + download URL. |
shopify_shopifyql | ShopifyQL analytics (SQL-like; requires read_reports). |
Every non-list tool takes an optional shop argument (alias or domain). Required when >1 store configured; auto-selected when exactly 1.
100% of Admin GraphQL API read surface — any object, field, or connection accessible with the token's scopes is reachable via shopify_graphql_query. Anything large-scale (>10k records) should use shopify_bulk_query. Analytics goes through shopify_shopifyql.
Set SHOPIFY_STORES to a JSON object mapping alias → store config:
{
"main": {"domain": "my-store.myshopify.com", "client_id": "...", "client_secret": "..."},
"outlet": {"domain": "my-store-outlet.myshopify.com", "client_id": "...", "client_secret": "..."},
"legacy": {"domain": "legacy-store.myshopify.com", "access_token": "shpat_..."}
}client_id+client_secret (Dev Dashboard OAuth, 24h tokens auto-refreshed per store) OR access_token (legacy shpat_).[a-z0-9][a-z0-9_-]{0,63}. Lowercase-normalized on load.If SHOPIFY_STORES is unset, the MCP falls back to single-store env vars:
SHOPIFY_DOMAIN or SHOPIFY_SHOP_DOMAIN — <shop>.myshopify.comSHOPIFY_CLIENT_ID + SHOPIFY_CLIENT_SECRET (Dev Dashboard)SHOPIFY_ACCESS_TOKEN (legacy shpat_)The single store registers under alias default — callers can omit shop argument on tool calls.
Every query is parsed with graphql-core before transmission. The parser rejects:
subscription operations (not supported by Admin API anyway)mutation EXCEPT bulkOperationCancel (cancels an in-flight bulk job, no shop-data write)bulkOperationRunQuery is NOT in the generic parser allowlist. Legitimate bulk exports go through the dedicated shopify_bulk_query tool, which validates the inner query with _assert_read_only BEFORE wrapping it in the bulk mutation. Single source of truth — no reliance on Shopify-side validation.
Per-store cost-based leaky bucket (Shopify's model). Each response includes extensions.cost.throttleStatus. On THROTTLED errors, the MCP sleeps ceil((requestedQueryCost - currentlyAvailable) / restoreRate) seconds (minimum 1s) and retries up to 3 times. Over limit → surfaced to caller.
Buckets are independent per store — throttle on store A doesn't affect store B.
^[a-z0-9][a-z0-9-]*\.myshopify\.com$ (no arbitrary hostnames — defeats SSRF).^[a-z0-9][a-z0-9_-]{0,63}$.allow_redirects=False on both token exchange and GraphQL calls.X-Shopify-Access-Token, client_secret, and all token prefixes (shpua_, shpat_, shpss_, shpca_) redacted from logs and error messages.shopify
Default pin: 2026-04. Callers can override per-tool via api_version="YYYY-MM". Bump the module constant SHOPIFY_API_VERSION quarterly after smoke-testing new versions.
Minimum viable: read_products read_orders read_customers.
Recommended baseline: read_products read_orders read_customers read_inventory read_locations read_fulfillments read_discounts read_content read_themes read_files read_markets read_metaobjects read_metaobject_definitions read_reports read_translations read_locales read_shipping.
Add read_all_orders for >60-day order history. Enable Protected customer data access in Dev Dashboard → Configuration if the agent needs customer PII.
This connector runs locally, on your own machine, under your own Shopify credentials. It is a thin read-only bridge between your MCP client and Shopify's Admin API.
*.myshopify.com), enforced by a domain allowlist.The canonical hosted version of this policy: https://scalably.io/connector-privacy.html
We write about building production MCP servers and AI agents at scalably.io/blog:
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.