Skip to main content
Glama
Cubitrek

shopify-multistore-mcp

by Cubitrek

shopify-multistore-mcp

An MCP server for working across multiple Shopify stores (multi-client dev work) through the Admin GraphQL API. Every tool takes a store argument that routes the call to the right client store, so one connector covers all your stores.

Designed to sit alongside Shopify's official tooling, not replace it:

Layer

What it does

You build it?

Shopify AI Toolkit / Dev MCP

Theme, apps, Functions, extensions, docs + GraphQL validation

No — install it

Shopify CLI

theme dev, app dev, Functions deploy (per-store auth)

No — install it

This server

Conversational Admin API read/write across stores, with a production write-gate

Yes — this repo

Quick start

git clone https://github.com/Cubitrek/shopify-multistore-mcp.git
cd shopify-multistore-mcp
npm install
npm run build
cp stores.example.json stores.json    # then edit — add your stores

Then, per store, get an access token and connect it to Claude. Check your config loaded:

STORES_CONFIG=./stores.json node -e \
  'import("./dist/config.js").then(m=>console.table(m.loadRegistry().list()))'

Every store shows authenticated: true or false, so you can see at a glance which ones still need a token.

Related MCP server: Shopify Store MCP Server

Tools

Reads: list_stores, shop_info, get_products, get_orders, get_metafields, get_pages

Writes (production-gated): set_metafields, add_tags, remove_tags, create_product, update_product, create_page, update_page

Escape hatch: graphql — run any Admin GraphQL query or mutation

The production write-gate

Each store is tagged env: "production" or env: "dev".

  • Mutations against a dev store run freely.

  • Mutations against a production store are blocked unless the call passes confirm: true. The model must inspect what will change, then explicitly confirm.

Failed writes surface their userErrors, so a rejected mutation can never look like a success.

Getting an access token

Which path you need depends on the store's relationship to your Partner organization — not on how old the store is. Check this first; picking wrong costs an hour of dead ends:

The store is…

Where you see it

Path

A merchant's own store you have collaborator access to, or your own live store

Dev Dashboard → Stores → Collaborations

Path A — OAuth capture

A client-transfer store still held in your Partner org

Dev Dashboard → Stores → Client transfer

Path B — legacy custom app

Already has a legacy custom app from before 2026

Store admin → Settings → Apps

Reuse it — copy the existing shpat_…

⚠️ Custom apps cannot be installed on client-transfer stores. Shopify restricts those to free and partner-friendly apps, so every install attempt fails with "The installation link for this app is invalid" — no matter how the app is configured. This is not a misconfiguration; Path A simply cannot work until the store is transferred to the merchant. Use Path B.

Path A — OAuth capture (current Shopify platform) ⭐

Shopify has retired legacy custom apps on current stores (Spring '26 and later). Apps are now created in the Dev Dashboard, are OAuth-based, and only hand the access token to a backend during install — there's no "reveal token" button anymore.

This repo ships that backend as a one-time command. npm run auth runs a throwaway local callback server, completes the install, and writes the resulting offline access token into stores.json. Offline tokens don't expire, so it's a single step per store — afterwards the MCP server behaves exactly as it would with a legacy static token.

1. Create the app in the Dev Dashboard (Partners → your org → Dev dashboard → Create app → Start from Dev Dashboard).

2. Configure the version: set your Admin API scopes, uncheck Embed app in Shopify admin (this is a headless connector), and add your redirect URL:

http://localhost:3456/callback

3. Set the distribution method. For a single client store, choose Custom distribution and enter that store's .myshopify.com domain. ⚠️ Distribution choice is permanent per app, and custom distribution binds the app to that one store — create a dedicated app per client store.

Custom distribution is what lets you install on a store that is not in your organization — e.g. a client store you only have collaborator access to. It does not work on client-transfer stores held in your own org; see the table above.

4. Configure this repo (stores.json) with the app's Client ID and scopes:

{
  "defaultApiVersion": "2026-07",
  "app": {
    "clientId": "your_client_id",
    "clientSecretEnv": "SHOPIFY_CLIENT_SECRET",
    "redirectUri": "http://localhost:3456/callback",
    "scopes": "read_products,write_products,read_orders,read_content,write_content"
  },
  "stores": {
    "client-a": {
      "label": "Client A (production)",
      "domain": "client-a.myshopify.com",
      "env": "production"
    }
  }
}

5. Put the client secret where the tool can find it. clientSecretEnv names an environment variable; the value belongs in .env (gitignored), which is loaded automatically on startup:

echo 'SHOPIFY_CLIENT_SECRET=shpss_...' >> .env

An existing environment variable always wins over .env, so SHOPIFY_CLIENT_SECRET=… npm run auth … and CI secrets still override the file.

Before this was wired up, .env was never read and npm run auth failed with "App for … has no client secret" even when the secret was sitting in the file. If you see that error, the named variable genuinely isn't reaching the process — check the name in clientSecretEnv matches the key in .env.

6. Run the capture:

npm run build
npm run auth -- --store=client-a

It prints an install URL. Open it in a browser signed into that store's admin, approve the scopes, and the token is written into stores.json automatically.

The callback is verified before any token is stored: HMAC signature (so a forged callback is rejected), state nonce (CSRF), and a shop-domain match against your config.

First install of a custom-distribution app

If Shopify answers "The installation link for this app is invalid" or Oauth error invalid_link, check the store type first: on a client-transfer store no install link will ever work, and you want Path B instead. Otherwise the app uses custom distribution, and its install link is signed by Shopify and cannot be generated locally — neither https://<shop>/admin/oauth/authorize?… nor https://admin.shopify.com/store/<handle>/oauth/install_custom_app?client_id=… will work, however correct they look.

Only the link on the app's own Distribution page works, and it must be copied by hand:

  1. Set the app's App URL to http://localhost:3456/ (Shopify opens it with ?shop=… and the local server answers that handshake). The redirect URL stays http://localhost:3456/callback.

  2. Start the listener and leave it running:

    npm run auth -- --store=client-a --install-link
  3. In the Dev Dashboard / Partners: Apps → your app → Distribution. Press Copy on the Install link, open it in a browser signed into the store admin, and press Install.

Because Shopify originates this flow, the callback carries Shopify's own state rather than a nonce we generated, so --install-link relaxes the state check. HMAC verification and the shop-domain match still apply. After the first install, the ordinary authorize URL works for re-auth.

⚠️ Scopes are fixed at install time. Adding a scope to stores.json later has no effect until you also add it to the app in the Dev Dashboard, release a new version, and re-run the capture. Verify what was actually granted:

curl -s -H "X-Shopify-Access-Token: $TOKEN" \
  https://<shop>.myshopify.com/admin/oauth/access_scopes.json

Path B — legacy custom app

Legacy custom apps mint a static shpat_… token directly in the store admin — no OAuth, no install link, no callback server. Much simpler than Path A when it's available.

As of January 1, 2026, merchants can no longer create them. Two exceptions:

  • Partners can still create them on stores they hold before transfer. This is the sanctioned path for client-transfer stores, and the only one that works there. Creation is disabled once the store transfers to the merchant.

  • Apps created before the cutoff keep working, including their tokens.

Store admin → Settings → Apps → App developmentCreate a legacy custom app. Name it, Configure Admin API scopes, tick what you need, Save, then Install app and reveal the token. Drop it into stores.json:

"woot": {
  "domain": "your-store.myshopify.com",
  "env": "production",
  "adminToken": "shpat_..."
}

A store using adminToken needs no app block — that's only read by npm run auth.

If the page shows only a pointer to the Dev Dashboard with no Create button, the store is merchant-owned and past the cutoff. Use Path A.

Configuration reference

{
  "defaultApiVersion": "2026-07",
  "app": { /* global OAuth app, used by `npm run auth` */ },
  "stores": {
    "alias": {
      "label": "Human-readable name",
      "domain": "store.myshopify.com",
      "env": "production",          // or "dev" — drives the write-gate
      "adminToken": "shpat_...",    // inline token, OR
      "adminTokenEnv": "SOME_VAR",  // read from env, OR
      "apiVersion": "2026-04",      // optional per-store override
      "app": { /* optional per-store app override */ }
    }
  }
}
  • A store with no token is fine — tools return a clear "run npm run auth" error, and list_stores reports authenticated: false.

  • The client secret is only needed for npm run auth, never to run the server.

  • stores.json is gitignored. Never commit real tokens.

Connect it to Claude

Add to your MCP config (Claude Desktop claude_desktop_config.json, or Claude Code .mcp.json) — this wires the official Dev MCP and this multi-store server:

{
  "mcpServers": {
    "shopify-dev": {
      "command": "npx",
      "args": ["-y", "@shopify/dev-mcp@latest"]
    },
    "shopify-multistore": {
      "command": "node",
      "args": ["/absolute/path/to/shopify-multistore-mcp/dist/index.js"],
      "env": {
        "STORES_CONFIG": "/absolute/path/to/shopify-multistore-mcp/stores.json"
      }
    }
  }
}

Or register it once for every project with the Claude Code CLI:

claude mcp add shopify-multistore --scope user \
  --env STORES_CONFIG=$PWD/stores.json \
  -- node $PWD/dist/index.js

If the server fails to start but works fine in your terminal, use an absolute path to node (e.g. /opt/homebrew/bin/node). Desktop apps don't inherit your shell's PATH, so version-manager shims like nvm often aren't on it.

Restart Claude after adding it, then ask "list my shopify stores" to confirm.

For theme/app/Functions work, also install the official Shopify AI Toolkit plugin and the Shopify CLI.

Usage examples

  • "List my configured stores."

  • "Show active products from vendor Acme in client-a."

  • "How many unfulfilled orders does client-a have this week?"

  • "Add a metafield custom.care_instructions to product X in dev-store." (runs)

  • "Update the price of variant Y in client-a." (production — asks to confirm first)

Development

npm run build       # compile to dist/
npm run dev         # tsc --watch
npm run typecheck   # type-check without emitting
npm run auth -- --store=<alias>   # one-time OAuth token capture

Project layout

src/
  index.ts    # MCP server entry (stdio)
  auth.ts     # one-time OAuth token capture (local callback server)
  config.ts   # store registry loader + zod validation
  dotenv.ts   # minimal .env loader (no dependency)
  shopify.ts  # Admin GraphQL client + production write-gate
  tools.ts    # tool definitions
stores.example.json  # template — copy to stores.json (gitignored)

Roadmap

  • Inventory adjustments, theme asset read/write, bulk operations

  • Token refresh / re-auth helper for rotated secrets

  • Optional hosted redirect endpoint (for a permanent, non-localhost install URL)

License

MIT

A
license - permissive license
-
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

View all related MCP servers

Related MCP Connectors

  • Manage your Savanto store from your AI: catalog, content, prompts, and analytics, by chat.

  • Shared, permission-aware company context for AI agents, with provenance, approvals and audit.

  • Runtime permission, approval, and audit layer for AI agent tool execution.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Cubitrek/shopify-multistore-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server