Skip to main content
Glama
aakarsh1t

Shopify Enterprise MCP Server

by aakarsh1t

Shopify Enterprise MCP Server

Production-grade Model Context Protocol server that exposes live Shopify Admin GraphQL data to Microsoft Copilot Studio agents.

Every business question is answered by querying Shopify at request time. The server never returns cached figures and instructs the connected agent never to answer from prior knowledge — inventory, orders and sales change continuously, so a remembered number is a wrong number.


Status

Complete. Twenty-one tools — seventeen read-only, four that change the store — implemented, unit tested and verified against a live Shopify store.

Area

State

Build, TypeScript strict mode, ESLint, Prettier

✅ Complete

Configuration + fail-fast environment validation

✅ Complete

Shopify authentication (client credentials + renewal)

✅ Complete

Error hierarchy + RFC 9457 global handler

✅ Complete

Structured logging + correlation context

✅ Complete

Application Insights telemetry

✅ Complete

Shopify GraphQL client (retry, cost, throttle, paging)

✅ Complete

MCP registry, server factory, Streamable HTTP transport

✅ Complete

Health / readiness / version endpoints

✅ Complete

All 21 MCP tools

✅ Complete

Store-changing tools, gated and confirmed

✅ Complete

Test suite

✅ 446 passing

Related MCP server: Clind MCP Server

Available MCP tools

Catalogue

Tool

Answers

search_products

"How many Nike shoes are in stock?", "Find products under 50", "What perfumes do we sell?"

get_product_details

Full record for one product: every variant, price, SKU, stock level, options and collections.

recommend_related_products

"What else would go with this?" — catalogue similarity, not purchase behaviour.

get_collections

"What collections do we have?", "Is that collection rule-based or curated?"

Orders, fulfilment and customers

Tool

Answers

get_orders

"How many orders this week?", "Show unfulfilled orders", "Any refunds?"

get_order_by_id

Everything about one order: line items, addresses, fulfilment, refunds.

get_fulfillments

"What has shipped?", "How long are we taking to despatch?", "No tracking?"

get_refunds

"How much have we refunded?", "What came back, and was it restocked?"

search_customers

"Who are our customers?", "Find customers tagged VIP", "Customers in India"

get_customer

One customer's full profile, addresses and lifetime value.

get_customer_purchase_history

"What has this person bought?", "When did they last order?"

Inventory

Tool

Answers

get_inventory

"What's in stock?", "Stock level for SKU X", "Anything oversold?"

get_low_stock_products

"What's running low?", "What do we need to reorder?"

Commercial

Tool

Answers

get_sales_summary

"How much did we sell last month?", "What's our average order value?"

get_best_selling_products

"What are our top sellers?", "Which product made the most revenue?"

get_discounts

"What promotions are running?", "Is the free shipping offer still active?"

get_abandoned_carts

"How many abandoned carts?", "What revenue are we losing at checkout?"

Every list tool filters and paginates server-side in Shopify. A price filter applied locally could only narrow the page that happened to be fetched, so "products under 50" would silently come to mean "products under 50 among the 20 that came back".

Tools that change the store

Tool

Effect

create_product

Adds a product. Created as DRAFT unless ACTIVE is asked for.

update_product

Changes title, description, vendor, type, tags or status.

update_inventory_levels

Sets or adjusts stock on hand, which decides what customers can buy.

create_refund

Returns real money to a customer. Cannot be undone.

Three independent controls stand in front of them:

  1. Registration gate. MCP_ENABLE_WRITE_TOOLS=false withholds all four from tools/list entirely — an agent cannot call what was never published. It is a single App Service setting, so the capability can be withdrawn during an incident without a redeploy.

  2. Explicit confirmation. Every one requires confirm: true. Called without it they change nothing and instead report exactly what would change — for stock, after reading the live level, so the user approves real before-and-after figures rather than an intention.

  3. userErrors enforcement. Shopify rejects a mutation with HTTP 200, a null payload and the reason in userErrors. Every mutation response is checked, so a write that did not happen is never reported as one that did.

create_refund adds two more: the amount is always computed by Shopify's own suggestedRefund rather than by this server, and MCP_MAX_REFUND_AMOUNT can cap any single refund.

Every completed change writes an audit line carrying the correlation ID; refunds log at warn.


Documentation

Document

Covers

architecture.md

Layering, request flow, and the reasoning behind each design decision

configuration.md

Every environment variable, production invariants, common misconfigurations

deployment.md

Full Azure App Service procedure, slots, scaling, rollback, checklist

copilot-studio.md

Connector import, agent wiring, grounding instructions, troubleshooting

copilot-studio-connector.yaml

Power Platform custom connector definition

tools.md

Each tool with real request/response examples

operations.md

Runbook: KQL queries, alerts, incident diagnosis, rotation


Architecture

┌──────────────────────┐   Streamable HTTP (JSON-RPC 2.0)   ┌────────────────────────────┐
│  Copilot Studio      │ ─────────────────────────────────► │  Azure App Service (Linux) │
│  custom connector    │        HTTPS + x-api-key           │  Fastify + MCP SDK         │
└──────────────────────┘                                    └─────────────┬──────────────┘
                                                                          │
                        ┌─────────────────────────────────────────────────┘
                        ▼
        ┌───────────────────────────┐   Correlation context (AsyncLocalStorage)
        │  MCP transport            │   flows through every layer below
        │  (stateless per request)  │
        └─────────────┬─────────────┘
                      ▼
        ┌───────────────────────────┐   Cross-cutting: validation, timing,
        │  Tool executor            │   envelope, error → result, telemetry
        └─────────────┬─────────────┘
                      ▼
        ┌───────────────────────────┐   Business logic only
        │  Service layer            │
        └─────────────┬─────────────┘
                      ▼
        ┌───────────────────────────┐   Retry · cost governor · pagination
        │  ShopifyGraphQLClient     │   error mapping · dependency telemetry
        └─────────────┬─────────────┘
                      ▼
             Shopify Admin GraphQL API

Layer rules

  • Tools contain no business logic. They validate input, call a service, and return a summary plus structured data.

  • Services contain no MCP or HTTP concepts. They accept typed arguments and return domain objects.

  • The Shopify client is the only egress point. Nothing else issues an outbound request.

  • Configuration is read once, validated, frozen. No module touches process.env directly.


Project structure

src/
  app/            Fastify composition root, plugins, routes, error handler
  config/         Environment schema, validated + frozen configuration
  mcp/
    registry/     Tool definitions and the process-wide tool catalogue
    server/       McpServer factory and the tool execution wrapper
    transport/    Streamable HTTP transport management
  tools/          MCP tool definitions (composition root)
  services/       Domain services (product, order, inventory, customer, …)
  shopify/
    graphql-client.ts   Retry, cost governance, error mapping, telemetry
    cost-governor.ts    Client-side model of Shopify's leaky bucket
    error-mapper.ts     HTTP + GraphQL failure classification
    pagination.ts       Bounded cursor pagination
    queries/            Reusable GraphQL documents
    types/              Connection, money and cost types
  middleware/
    auth/           API key authentication
    logging/        Pino logger and AsyncLocalStorage request context
    telemetry/      Application Insights behind a vendor-neutral port
  errors/         Error hierarchy, codes, normaliser
  schemas/        Shared Zod schemas
  utils/          Identifiers, timing, backoff
  types/          Shared type declarations
tests/
  unit/ integration/ mocks/ helpers/
docs/

Getting started

Prerequisites

  • Node.js 22 LTS (the toolchain also runs on Node 24)

  • npm 10+

  • A Shopify app created in the Dev Dashboard, installed on your store

Install and run

npm install
cp .env.example .env       # then fill in the Shopify values
npm run dev                # watch mode
npm run build && npm start # production build

Verify

npm run verify             # typecheck + lint + tests
npm run test:coverage      # coverage report
curl http://localhost:8080/health
curl http://localhost:8080/ready
curl http://localhost:8080/version

MCP handshake:

curl -X POST http://localhost:8080/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{
        "protocolVersion":"2025-06-18","capabilities":{},
        "clientInfo":{"name":"curl","version":"1.0"}}}'

Getting the Shopify credentials

Shopify has retired legacy custom apps. The store admin no longer offers "Create an app", and there is no long-lived Admin API token to copy anywhere. Apps are now created in the Shopify Dev Dashboard, and credentials are exchanged for a short-lived access token through the OAuth client credentials grant.

  1. Create an app in the Dev Dashboard and install it on your store.

  2. Open your app → Settings → Credentials and copy the Client ID and Secret (shpss_…).

  3. Set them as SHOPIFY_CLIENT_ID and SHOPIFY_CLIENT_SECRET.

The server exchanges them for a token at startup and renews it automatically. Verify manually with:

curl -X POST https://<store>.myshopify.com/admin/oauth/access_token \
  -d grant_type=client_credentials \
  -d client_id=<CLIENT_ID> -d client_secret=<CLIENT_SECRET>
# -> {"access_token":"shpat_…","scope":"…","expires_in":86399}

expires_in is 24 hours. This is why a pasted SHOPIFY_ADMIN_ACCESS_TOKEN is rejected in production: it works the day it is set and starts returning HTTP 401 the next.

The grant requires the app and the store to be in the same Shopify organization. If they are not, Shopify answers invalid_client — which looks identical to a wrong secret.

Required read scopes: read_products, read_inventory, read_locations, read_orders, read_all_orders, read_customers, read_discounts, read_price_rules, read_analytics, read_marketing_events.

Grant read scopes only. This server is read-only and advertises readOnlyHint: true, but the credential itself is not constrained by that — an app granted write_products holds the ability to delete a catalogue, on a connection whose other end is an LLM.


Configuration

Every variable is validated at startup. A misconfigured deployment fails immediately with every offending variable listed at once, rather than failing on the first one or booting half-configured.

See .env.example for the full annotated contract. The essentials:

Variable

Required

Default

Purpose

SHOPIFY_STORE

Yes

Store handle or *.myshopify.com domain

SHOPIFY_CLIENT_ID

Yes¹

Dev Dashboard app client ID

SHOPIFY_CLIENT_SECRET

Yes¹

Dev Dashboard app secret (shpss_…)

SHOPIFY_ADMIN_ACCESS_TOKEN

Local dev only¹

Pre-minted token; expires in 24h

SHOPIFY_API_VERSION

No

2026-07

Quarterly Admin API version

PORT

No

8080

Injected by App Service

MCP_HTTP_PATH

No

/mcp

Must match the connector's declared path

MCP_API_KEY

In production

Shared secret for the connector

MCP_ALLOWED_HOSTS

In production

DNS-rebinding protection allowlist

APPINSIGHTS_CONNECTION_STRING

In production

Application Insights

LOG_PRETTY

Must be false in production

false

Human-readable logs (local only)

¹ Supply either the client credentials pair or a static token. Startup fails if neither is present. When both are given, client credentials win — they are the only renewable option — and the active mode is stated in the startup log and on GET /version.

Production invariants

The server refuses to start in NODE_ENV=production when any of these hold:

  • no MCP_API_KEY — an unauthenticated MCP endpoint exposes the store's entire order and customer dataset to anonymous callers;

  • no APPINSIGHTS_CONNECTION_STRING;

  • no MCP_ALLOWED_HOSTS;

  • LOG_PRETTY=true;

  • a static SHOPIFY_ADMIN_ACCESS_TOKEN instead of client credentials — it would expire within 24 hours with no way to renew itself.

These are configuration errors, not warnings.


Operational endpoints

Endpoint

Question answered

Behaviour

/health

Is the process alive?

Always 200 while running. No I/O.

/ready

Can this instance serve a request?

200 / 503 based on a cached, 1-cost-point Shopify probe.

/version

Which build, store, API version, tools?

Build identity and the published tool catalogue.

Configure the App Service health check against /health, not /ready. Pointing the platform probe at a dependency-aware endpoint makes App Service recycle healthy workers during a Shopify incident, turning a degraded service into an outage. Use /ready for deployment gates and load balancer decisions.


Design decisions worth knowing

Stateless MCP sessions by default. A fresh McpServer is built per request, so any App Service instance can serve any request and scale-out needs no ARR affinity. MCP_SESSION_MODE=stateful is available but requires session affinity.

Tokens are minted and renewed by the server. Shopify's client credentials grant issues 24-hour tokens. The provider caches one in memory, renews it 5 minutes before expiry, and collapses concurrent callers onto a single in-flight mint so a burst of tool calls on a cold instance does not trigger a token request each. If Shopify rejects a token early — after a credential rotation — the client discards it and retries exactly once, outside the retry budget, so recovery also works for callers that disable retries (such as the readiness probe).

Client-side cost governance. Shopify meters GraphQL by query cost against a leaky bucket (1000 points refilling at 50/s on standard plans; the live test store reported 4000 at 200/s). The client reads extensions.cost from every response, adopts whatever limits the store actually reports, and waits for headroom before dispatching — rather than absorbing a THROTTLED error and retrying into an empty bucket.

Search filters come from an allowlist. Shopify silently ignores filter fields it does not recognise: price:<50 filters correctly, while variants.price:<50 is dropped and returns unfiltered products with HTTP 200 and no error at all. An agent would then relay those results as though they satisfied the filter. Every field name the query builder emits is drawn from a fixed list verified against the live API, and values are escaped and quoted.

Stock figures are reported honestly. Shopify returns totalInventory: null for untracked products, which is not the same as zero in stock, so the domain model keeps a separate inventoryTracked flag. Live data also showed a product reporting totalInventory: 0 while its variants stood at -31, -19 and -23; the summary states the oversell rather than repeating the zero.

Throttling arrives as HTTP 200. The Admin API reports throttling as a successful HTTP response with errors[].extensions.code === "THROTTLED". The client checks the GraphQL error array and the HTTP status, and classifies each failure mode for retryability independently.

Tool failures are returned, not thrown. A thrown error becomes a JSON-RPC protocol error, which Copilot Studio surfaces as an opaque connector failure the agent cannot reason about. Tool failures are returned as isError: true results carrying the reason, retryability, remediation and correlation ID, so the agent can explain the problem to the user.

Uniform response envelope. Every tool returns { summary, resultCount, truncated, data }. summary gives the agent a grounded sentence it can relay verbatim; truncated lets it state honestly that a figure is a bounded sample rather than a store-wide total.

Correlation without plumbing. Correlation IDs propagate through AsyncLocalStorage, so the domain layer carries no context parameters, yet every log line, telemetry item and Shopify call is attributable to the originating request.


Copilot Studio compatibility

  • Streamable HTTP only. Copilot Studio dropped SSE transport support in August 2025.

  • Custom connector with x-ms-agentic-protocol: mcp-streamable-1.0 on a POST operation.

  • HTTPS, terminated by App Service.

  • Accept header shim. The MCP specification requires clients to accept both application/json and text/event-stream. Power Platform connectors may forward only application/json, which the SDK rejects with HTTP 406 — surfacing in Copilot Studio as an unexplained connector failure. The MCP route widens the header (on rawHeaders, which is what the transport actually reads).

  • Tool metadata is validated at registration: snake_case names and descriptions long enough to drive orchestrator routing. Both input and output schemas are published.

Connector setup, the OpenAPI schema and Azure deployment steps are documented in a later milestone.


Testing

npm test              # all suites
npm run test:unit
npm run test:integration
npm run test:coverage

Integration tests drive the real Fastify instance via app.inject() — correlation hooks, security plugins, error handler, route wiring and the MCP SDK transport all execute; only the network is absent. Shopify is substituted at the transport seam, so retry sequencing, cost accounting and error mapping are exercised for real.


Scripts

Script

Purpose

npm run dev

Watch mode via tsx

npm run build

Clean + compile to dist/

npm start

Run the compiled server

npm run verify

Typecheck + lint + test

npm run typecheck

tsc --noEmit

npm run lint

ESLint, zero warnings tolerated

npm run format

Prettier


Troubleshooting

Symptom

Cause and fix

Startup fails listing environment variables

Working as designed. Fix every listed variable; all problems are reported at once.

/ready reports SHOPIFY_AUTHENTICATION_FAILED

Wrong client credentials, app and store in different Shopify organizations, or a missing read scope.

/ready reports SHOPIFY_API_ERROR with a 404 hint

SHOPIFY_STORE or SHOPIFY_API_VERSION is wrong; an unsupported version returns 404.

HTTP 406 from /mcp

The client sent a narrow Accept header. The shim handles the common case; check for a proxy rewriting headers.

HTTP 401 from /mcp

MCP_API_KEY mismatch. The connector must send it as x-api-key or Authorization: Bearer.

Frequent SHOPIFY_THROTTLED warnings

The store's cost budget is under pressure. Raise SHOPIFY_MIN_COST_BUFFER or lower page sizes.

Logs are unreadable JSON locally

Set LOG_PRETTY=true. It is rejected in production, where JSON is required.

Every error response and tool failure carries a correlation ID. Search Application Insights on customDimensions.correlationId to retrieve the full request trace, including each Shopify call, its cost and its outcome.

F
license - not found
-
quality - not tested
C
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

  • Hosted Amazon Seller and Vendor MCP server for Claude, ChatGPT, Cursor, Codex, Gemini, Copilot.

  • Shopify MCP Pack — wraps the Shopify Admin REST API (2024-01)

  • Hosted Amazon Seller Central and Amazon Ads MCP server for Claude, ChatGPT, Cursor, and agents.

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/aakarsh1t/ShopifyMCP'

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