# Shopify MCP: an MCP server by Scalably

Canonical: https://scalably.io/mcp/shopify-mcp
Source: https://github.com/scalably-io/shopify-mcp
Registry: https://registry.modelcontextprotocol.io/v0.1/servers/io.scalably%2Fshopify-mcp/versions/1.1.0
Provenance: derived from `container/tools/shopify-mcp/server.py` at `ef174fc3` (2026-08-31) in the private ScalablyAI repository
Integrity: sha256 of the raw README served at https://scalably.io/mcp/shopify-mcp.md is 2258085a52d7485b2d14e9919f6ea69cd12fdefcf6aec84e140c6cb24783ac5c. Verify: curl -s https://scalably.io/mcp/shopify-mcp.md | shasum -a 256
This is the machine-readable representation of the page at the canonical URL. Same facts, denser format.

## Direct answer
Read-only Shopify Admin GraphQL MCP: query, introspect, bulk export, ShopifyQL. 6 tools.

Read-only Admin GraphQL access for store analytics and catalog audits.

## Install
- Claude Code: `claude mcp add shopify -e SHOPIFY_DOMAIN=my-store.myshopify.com -e SHOPIFY_ACCESS_TOKEN=shpat_... -- uvx scalably-shopify-mcp`
- Codex: `codex mcp add shopify --env SHOPIFY_DOMAIN=my-store.myshopify.com --env SHOPIFY_ACCESS_TOKEN=shpat_... -- uvx scalably-shopify-mcp`
- Claude Desktop: download `shopify-mcp.mcpb` from the latest GitHub release and open it.

## Environment
| Variable | Required | Secret | Purpose |
|---|---|---|---|
| `SHOPIFY_STORES` | no | yes | JSON object mapping alias to store config for multi-store setups; holds tokens, treat as a secret |
| `SHOPIFY_SHOP_DOMAIN` | no | no | Single-store admin domain, '<shop>.myshopify.com'; ignored if SHOPIFY_STORES is set |
| `SHOPIFY_CLIENT_ID` | no | no | Dev Dashboard custom-app Client ID; required unless SHOPIFY_ACCESS_TOKEN is set |
| `SHOPIFY_CLIENT_SECRET` | no | yes | Dev Dashboard custom-app Client Secret; required unless SHOPIFY_ACCESS_TOKEN is set |
| `SHOPIFY_ACCESS_TOKEN` | no | yes | Legacy shpat_ access token; alternative to Client ID/Secret |

## Tools
| Tool | Description |
|---|---|
| `shopify_list_stores` | List all Shopify stores configured for this agent (alias, domain, auth mode). Call first. |
| `shopify_graphql_query` | Execute a read-only GraphQL query against the Shopify Admin API. Mutations rejected by the parser. |
| `shopify_graphql_introspect` | Introspect the Admin GraphQL schema, full catalog or a single type. |
| `shopify_bulk_query` | Launch an async bulk export of a read-only GraphQL query. Returns a BulkOperation ID. |
| `shopify_bulk_poll` | Poll a BulkOperation by ID for status and download URL. |
| `shopify_shopifyql` | Run a ShopifyQL analytics query (SQL-like, requires read_reports). |

## Setup
### Single store (simplest)

- `SHOPIFY_DOMAIN` or `SHOPIFY_SHOP_DOMAIN` - `<shop>.myshopify.com`
- Auth path A: `SHOPIFY_ACCESS_TOKEN` (legacy `shpat_`)
- Auth path B: `SHOPIFY_CLIENT_ID` + `SHOPIFY_CLIENT_SECRET` (Dev Dashboard custom app, client-credentials OAuth, 24h tokens auto-refreshed)

The single store registers under alias `default`; callers can omit the `shop` argument on tool calls.

### Multi-store (agency setups)

Set `SHOPIFY_STORES` to a JSON object mapping alias to store config:

```json
{
  "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_..."}
}
```

- Each store can use either `client_id` + `client_secret` (Dev Dashboard OAuth) or `access_token` (legacy `shpat_`).
- Aliases: `[a-z0-9][a-z0-9_-]{0,63}`, lowercase-normalized on load.
- Token cache is per-store-domain; one throttled store doesn't block others.

### Scopes needed (read-only)

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 order history older than 60 days. Enable Protected customer data access in Dev Dashboard, Configuration, if the agent needs customer PII.

## Limits
100KB query size ceiling. Bulk exports: exactly one top-level connection per query, max 5 total connections, max depth 2, every nested connection node selects `id` without an alias; one bulk operation at a time per shop on API versions through 2025-10, up to 5 on 2026-01 and later. API version defaults to `2026-04`; override per call with `api_version="YYYY-MM"`.

## Reply shape
Tool replies mirror the underlying call rather than a uniform envelope. `shopify_list_stores` returns a JSON array of `{alias, domain, name, currency, auth_mode}`; every GraphQL-backed tool (`shopify_graphql_query`, `shopify_graphql_introspect`, `shopify_bulk_query`, `shopify_bulk_poll`, `shopify_shopifyql`) returns the raw Shopify Admin API response, `{"data": ..., "errors": ..., "extensions": ...}`, unwrapped. Tool-level failures (bad input, redacted transport errors) raise a plain error.

## Reliability
- CI: https://github.com/scalably-io/shopify-mcp/actions/workflows/validate.yml
- Release 1.1.0
- Verified 2026-08-31
- Each of ours started as an internal ScalablyAI MCP server. We stripped the internal envelope, the auth wrapper and client dispatch that only make sense inside our own platform, and kept the tool surface. Every release runs through a clean-install CI job, publishes under the io.scalably namespace with trusted publishing so no long-lived token ever leaves our CI, and registers on the official MCP registry.

## Related
- Read more: [How to build an MCP server in Python (production guide)](https://scalably.io/blog/how-to-build-mcp-server-python), [Claude Code MCP: how to add an MCP server](https://scalably.io/blog/claude-code-mcp), [MCP Inspector: how to debug and test your MCP server](https://scalably.io/blog/mcp-inspector-debug-mcp-server), [What is an MCP server? A clear explainer for developers](https://scalably.io/blog/what-is-an-mcp-server)
