Read-only Shopify Admin GraphQL MCP: query, introspect, bulk export, ShopifyQL. 6 tools.
Runs on your machine from its package; the steps are below.
At most: only reads or looks up information.
Odel charges nothing per call. The publisher may charge for its own service.
Runs locally over stdio
This server isn't hosted — your MCP client launches it from a package registry. Use one of the commands below, or drop the config into your client (e.g. Claude Desktop).
Run
uvx scalably-shopify-mcp
MCP client config
{
"mcpServers": {
"shopify-mcp": {
"command": "uvx",
"args": [
"scalably-shopify-mcp"
]
}
}
}Distributed as an MCP bundle (`https://github.com/scalably-io/shopify-mcp/releases/download/v1.1.0/shopify-mcp.mcpb`) — download it and import the bundle in your MCP client.
A single MCP server exposing the full 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)
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.
SHOPIFY_DOMAIN or SHOPIFY_SHOP_DOMAIN - <shop>.myshopify.comSHOPIFY_ACCESS_TOKEN (legacy shpat_)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.
Set SHOPIFY_STORES to a JSON object mapping alias to 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) or access_token (legacy shpat_).[a-z0-9][a-z0-9_-]{0,63}, lowercase-normalized on load.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.
| Tool | What it does |
|---|---|
shopify_list_stores | List all Shopify stores configured for this agent. Call first. |
shopify_graphql_query | Arbitrary read-only GraphQL. Mutations rejected by the parser. |
shopify_graphql_introspect | Schema introspection, full catalog or a single type. |
shopify_bulk_query | Launch an async bulk export (JSONL). |
shopify_bulk_poll | Poll a bulk operation status and 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 more than one store is configured; auto-selected when exactly one.
The full 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 (more than 10k records) should use shopify_bulk_query. Analytics goes through shopify_shopifyql.
| Variable | Required | Purpose |
|---|---|---|
SHOPIFY_DOMAIN, SHOPIFY_SHOP_DOMAIN | one of these or SHOPIFY_STORES | Single-store admin domain, <shop>.myshopify.com |
SHOPIFY_ACCESS_TOKEN | see above | Legacy shpat_ access token (single-store auth path B) |
SHOPIFY_CLIENT_ID, SHOPIFY_CLIENT_SECRET | see above | Dev Dashboard custom-app credentials (single-store auth path A) |
SHOPIFY_STORES | no | JSON object mapping alias to store config; takes precedence over the single-store variables above |
SHOPIFY_STORE_<ALIAS>_DOMAIN, _CLIENT_ID, _CLIENT_SECRET, _ACCESS_TOKEN | no | Prefix-key alternative to SHOPIFY_STORES for multi-store setups; one set of keys per store alias |
SHOPIFY_REQUEST_TIMEOUT_SECONDS | no | HTTP request timeout in seconds (default 60) |
SHOPIFY_LOG_LEVEL | no | INFO (default) or DEBUG |
Every query is parsed with graphql-core before transmission. The parser rejects:
subscription operations (not supported by the 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 the same read-only check 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 server sleeps ceil((requestedQueryCost - currentlyAvailable) / restoreRate) seconds (minimum 1s) and retries up to 3 times before surfacing the error. Buckets are independent per store: a throttle on one store doesn't affect another.
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.
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".
Each release lists the package version, the .mcpb sha256 and the production commit it was derived from in CHANGELOG.md. CI runs the tests and a clean install of the built wheel on every push.
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
MIT. Copyright Scalably.
We write about building production MCP servers and AI agents at scalably.io/blog: