Skip to main content
Glama
README.md

```
███████╗██╗  ██╗ ██████╗ ██████╗ ██╗███████╗██╗   ██╗    ███╗   ███╗ ██████╗██████╗ 
██╔════╝██║  ██║██╔═══██╗██╔══██╗██║██╔════╝╚██╗ ██╔╝    ████╗ ████║██╔════╝██╔══██╗
███████╗███████║██║   ██║██████╔╝██║█████╗   ╚████╔╝     ██╔████╔██║██║     ██████╔╝
╚════██║██╔══██║██║   ██║██╔═══╝ ██║██╔══╝    ╚██╔╝      ██║╚██╔╝██║██║     ██╔═══╝ 
███████║██║  ██║╚██████╔╝██║     ██║██║        ██║       ██║ ╚═╝ ██║╚██████╗██║     
╚══════╝╚═╝  ╚═╝ ╚═════╝ ╚═╝     ╚═╝╚═╝        ╚═╝       ╚═╝     ╚═╝ ╚═════╝╚═╝     
```

# Shopify MCP

A Model Context Protocol (MCP) server that connects agents to the Shopify Admin GraphQL API. Use it to browse, edit, and clean up store data via a curated set of tools.

**npm:** `shopify-mcp`  
**binary:** `shopify-mcp`

## Highlights

- CRUD for products, collections, orders, and customers
- Draft orders for quotes, manual orders, and B2B pricing
- Inventory and location lookups for stock workflows
- Metafields for custom data
- Metaobject entry creation and lookup for existing definitions
- URL redirects management
- OAuth login flow with local token caching
- Bulk product cleanup utilities
- Fail-closed read-only mode for audit and QA agents

## Prerequisites

- Node.js 18+
- A Shopify custom app (OAuth or Admin API token)

## Local setup (this repo)

Use this when you want to run the MCP server from this local checkout instead of a remote deployment.

1. Install dependencies and build:

```bash
npm install
npm run build
```

2. Create local env config:

```bash
cp .env.example .env
```

Set at least:
- `MYSHOPIFY_DOMAIN=your-store.myshopify.com`
- Either:
  - `SHOPIFY_CLIENT_ID=...` and `SHOPIFY_CLIENT_SECRET=...` for a Dev Dashboard app owned by the store's organization; or
  - `SHOPIFY_ACCESS_TOKEN=shpat_xxx` for a static/manual token
- `REMOTE_MCP=false`

3. Start local MCP (stdio):

```bash
npm run start:local
```

`start:local` uses stdio mode. Remote mode is only enabled with `--remote` or `REMOTE_MCP=true`.

### Read-only mode

Start a capability-restricted server for QA, audit, and reporting agents:

```bash
shopify-mcp --read-only --domain=<YOUR_SHOP>.myshopify.com
# or
SHOPIFY_MCP_READ_ONLY=true npm run start:local
```

Read-only mode exposes only a reviewed allowlist of lookup and reporting tools.
All mutating, mixed read/write, file-upload, and unknown future tools are hidden.
The allowlist is fail-closed, so a newly added tool does not appear in a
read-only instance until it is explicitly reviewed. `get-status` reports the
effective access mode, whether the boundary is enforced, and whether write
tools are exposed.

Use a least-privilege Shopify token as well when one is available. The server
boundary is designed to remain useful when a deployment must temporarily share
an existing token: agents connected to the read-only MCP instance cannot call
the hidden mutation tools.

## Install + run

### Client credentials (same Shopify organization)

For a Dev Dashboard app installed on a store owned by the same Shopify
organization, configure:

```bash
MYSHOPIFY_DOMAIN=your-store.myshopify.com
SHOPIFY_CLIENT_ID=your-client-id
SHOPIFY_CLIENT_SECRET=your-client-secret
```

No callback URL or browser consent is required. The MCP obtains Shopify's
24-hour access token at startup, caches it per permanent MyShopify domain in
`~/.shopify-mcp/tokens.json`, and renews it automatically before expiry.
Cached tokens include the client ID so a token created by a different app is
never silently reused after an app cutover.

Shopify does not allow this grant for public or custom-distribution apps on
stores owned by another organization; use the authorization-code flow below
for those apps.

### Authorization-code OAuth

1. Create a custom app and copy **Client ID** and **Client Secret**.
2. In **App setup**, set **App URL** and **Allowed redirection URLs** to:
   `http://localhost:3456/callback`
3. Start the OAuth flow:

```bash
npx shopify-mcp --oauth --domain=your-store.myshopify.com --clientId=xxx --clientSecret=yyy
```

Tokens are stored at `~/.shopify-mcp/tokens.json`. After that, start the server with just the domain:

```bash
npx shopify-mcp --domain=your-store.myshopify.com
```

Optional: override scopes with `--scopes` or `SHOPIFY_SCOPES`.

### Access token (manual)

1. Create a custom app in Shopify
2. Enable Admin API scopes:
   - `read_products`, `write_products`
   - `read_customers`, `write_customers`
   - `read_orders`, `write_orders`
   - `read_draft_orders`, `write_draft_orders`
   - `read_inventory`, `write_inventory`
   - `read_locations`
   - `read_content`, `write_content`
   - `read_files`, `write_files`
3. Install the app and copy the Admin API access token

Run:

```bash
shopify-mcp --accessToken=<YOUR_ACCESS_TOKEN> --domain=<YOUR_SHOP>.myshopify.com
```

## MCP client setup

### Claude Desktop (local repo build)

Build first (`npm run build`), then point Claude Desktop at this repo's built entrypoint:

```json
{
  "mcpServers": {
    "shopify-local": {
      "command": "node",
      "args": [
        "/absolute/path/to/shopify-mcp/dist/index.js",
        "--domain",
        "your-store.myshopify.com"
      ],
      "env": {
        "SHOPIFY_ACCESS_TOKEN": "shpat_xxx",
        "REMOTE_MCP": "false"
      }
    }
  }
}
```

If you completed OAuth locally, remove `SHOPIFY_ACCESS_TOKEN` and keep `--domain`.

### Claude Desktop (npm package)

```json
{
  "mcpServers": {
    "shopify": {
      "command": "npx",
      "args": [
        "shopify-mcp",
        "--accessToken",
        "<YOUR_ACCESS_TOKEN>",
        "--domain",
        "<YOUR_SHOP>.myshopify.com"
      ]
    }
  }
}
```

If you completed OAuth, omit `--accessToken` and keep `--domain`.

Config paths:
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
- Windows: `%APPDATA%/Claude/claude_desktop_config.json`

### Remote MCP (Railway, etc.)

By default this server runs as a local stdio MCP. Passing `--remote` (or setting
`REMOTE_MCP=true`) switches it to HTTP/SSE mode so it can be deployed as a remote
MCP server for Claude.ai or other remote clients. This repo ships a `Dockerfile`
and `railway.json` so Railway builds and starts it in remote mode out of the box.

**1. Get a token locally (one-time):**
```bash
npx shopify-mcp --oauth --domain=your-store.myshopify.com --clientId=xxx --clientSecret=yyy
# Token saved to ~/.shopify-mcp/tokens.json
```

**2. Deploy to Railway:**
- Create a project from this repo. Railway reads `railway.json` and builds the
  `Dockerfile`, which starts the server with `--remote`.
- Set the service environment variables (Railway injects `PORT` automatically):

```bash
SHOPIFY_ACCESS_TOKEN=shpat_xxx           # from tokens.json (or use the OAuth vars)
MYSHOPIFY_DOMAIN=your-store.myshopify.com
MCP_API_KEY=choose-a-long-random-string  # required to authenticate remote clients
# REMOTE_MCP=true is already implied by the Dockerfile's --remote flag
# PORT is injected by Railway (defaults to 3000 when run locally)
```

**3. Connect:**
- Health check: `GET /health`
- MCP endpoint (SSE): `GET /mcp?apiKey=<MCP_API_KEY>`
- Messages: `POST /messages?apiKey=<MCP_API_KEY>`

Auth uses the `apiKey` **query parameter**; requests without a matching
`MCP_API_KEY` receive `401`.

**Test the container locally before deploying:**
```bash
docker build -t shopify-mcp .
docker run -p 3000:3000 \
  -e MYSHOPIFY_DOMAIN=your-store.myshopify.com \
  -e SHOPIFY_ACCESS_TOKEN=shpat_xxx \
  -e MCP_API_KEY=test \
  shopify-mcp
# then in another shell: curl localhost:3000/health
```

### Environment variables (optional)

```bash
SHOPIFY_ACCESS_TOKEN=your_access_token
MYSHOPIFY_DOMAIN=your-store.myshopify.com
# Optional OAuth values:
# SHOPIFY_CLIENT_ID=your_client_id
# SHOPIFY_CLIENT_SECRET=your_client_secret
# SHOPIFY_SCOPES=comma,separated,scopes
# Hide every mutating or unreviewed tool (also available as `--read-only`):
# SHOPIFY_MCP_READ_ONLY=true
```

## Tool catalog

### Products
- `products` — unified lookup/search/filter. Pass `id` for a single product; omit `id` to list/search with filters (`title`, `status`, `vendor`, `tag`, inventory, dates, `hasImages`, …). Returns the product's Shopify Standard Product Taxonomy `category` (`{id, name, fullName}`) in `slim`/`standard`/`full`. Page size capped at 100.
- `create-product`
- `update-product` — accepts `category` (Shopify Standard Product Taxonomy GID, `vp-*` prefix); the tool verifies the category actually stuck and throws a loud, actionable error if Shopify silently rejected the GID, instead of leaving you with a null `category`.
- `delete-product`
- `delete-variant`
- `delete-product-images`
- `bulk-update-products`
- `bulk-delete-products`
- `count-products-by-tag`
- `find-products-by-metafield` — list products that have / don't have / both for a given `namespace.key`, paginated across the whole catalog via cursor
- `search-taxonomy` — browse Shopify's product category taxonomy; set `includeAttributes:true` to also return each category's attributes (e.g. Color, Pattern) and their allowed values

### Collections
- `get-collections`
- `manage-collection-products`
- `create-collection`
- `update-collection`
- `delete-collection`

### Customers
- `get-customers` (supports pagination via `cursor`)
- `update-customer`

### Orders
- `orders` — unified lookup/list. Pass `id` for a single order; omit `id` to list with filters (`customerId`, `status`, pagination via `cursor`). Replaces `get-orders`, `get-order-by-id`, and `get-customer-orders`.
- `update-order`

### Draft Orders
- `draft-orders` — unified lookup/list. Pass `id` for a single draft order; omit `id` to list with filters (`status`, `query`, pagination via `cursor`).
- `create-draft-order`
- `update-draft-order`
- `complete-draft-order`

### Inventory
- `get-inventory-levels`
- `update-inventory`

### Locations
- `get-locations`

### Metafields
- `get-metafields` — server-side filter with `key`+`namespace` (single field) or `keys: ["namespace.key", …]` (multi) via Shopify's native `metafields(keys:)`; set `includeDefinitions:true` to merge ALL definitions with current values so empty/unfilled fields show up (`value:null`, `isSet:false`)
- `set-metafield` (create or update; supports `metaobject_reference` / `list.metaobject_reference`)
- `bulk-set-variant-metafields` — set metafields across many variants of one product in a single `productVariantsBulkUpdate` call (up to 250 variants/call). UNIFORM mode (`metafields`) fans one value out to every variant and auto-discovers the variant IDs; PER-VARIANT mode (`variants`) sets different values per variant. Avoids one `set-metafield` call per variant.
- `delete-metafield`
- `list-metafield-definitions` — discover metafield definitions for an owner type (PRODUCT, ORDER, CUSTOMER, …); each entry now includes `constraints` (e.g. `{key:"category", values:["vp-2","vp-2-2-3", …]}`) so agents can see category-gating *before* writing (e.g. `vehicle_*` requires `vp-2*` Vehicle categories; values on disallowed categories are silently filtered out by Shopify on read).
- `get-metafield-options` — resolve a metafield's selectable options in one call (for metaobject-reference fields, returns the available metaobject entries; for choice-lists, the allowed choices)

### Metaobjects
- `list-metaobject-definitions`
- `get-metaobject-definition`
- `create-metaobject` — optional `status` (`ACTIVE`/`DRAFT`); defaults to Shopify's `DRAFT` for publishable definitions, pass `ACTIVE` to publish on create
- `update-metaobject` — edit fields on an existing entry (only provided keys change); optional `status` to publish (`ACTIVE`) or unpublish (`DRAFT`)
- `delete-metaobject`
- `list-metaobjects` — returns `status` per entry; optional `status` filter (applied client-side to the fetched page)
- `get-metaobject` — returns the entry's publish `status`

### Files
- `get-files` — list/search files in the store
- `attach-file-to-product` — attach an existing media file to a product
- `detach-file-from-product` — remove a media file from a product
- `create-file-upload-session` — start a browser upload session (**remote mode only**)
- `get-file-upload-session` — check an upload session (**remote mode only**)

### URL redirects
- `get-redirects`
- `create-redirect`
- `delete-redirect`

### Analytics
- `get-store-counts` - Get all key counts in one call (products, variants, orders, customers, collections)
- `get-product-issues` - Audit products for problems (zero inventory, low stock, missing images, zero price)

### Bulk Operations
- `start-bulk-export` - Start async bulk export (products, orders, customers, inventory, or custom query)
- `get-bulk-operation-status` - Check progress of bulk operation
- `get-bulk-operation-results` - Download and parse completed results (summary, sample, or full)

### Server
- `get-status` - Report MCP server status, configured store, and connection health

## Debugging

Tail Claude Desktop logs:

```bash
tail -n 20 -f ~/Library/Logs/Claude/mcp*.log
```

## License

MIT