@benwmerritt/shopify-mcp 0.0.0-stage → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +411 -2
- package/dist/config.js +49 -0
- package/dist/files/shopifyFiles.js +239 -0
- package/dist/files/uploadPipeline.js +155 -0
- package/dist/files/uploadSessions.js +58 -0
- package/dist/files/uploadUtils.js +20 -0
- package/dist/index.js +541 -0
- package/dist/oauth.js +312 -0
- package/dist/toolAccess.js +79 -0
- package/dist/toolRegistry.js +185 -0
- package/dist/tools/attachFileToProduct.js +81 -0
- package/dist/tools/bulkDeleteProducts.js +77 -0
- package/dist/tools/bulkSetVariantMetafields.js +237 -0
- package/dist/tools/bulkUpdateProducts.js +147 -0
- package/dist/tools/completeDraftOrder.js +103 -0
- package/dist/tools/countProductsByTag.js +47 -0
- package/dist/tools/createCollection.js +165 -0
- package/dist/tools/createDraftOrder.js +262 -0
- package/dist/tools/createFileUploadSession.js +41 -0
- package/dist/tools/createMetafieldDefinition.js +163 -0
- package/dist/tools/createMetaobject.js +113 -0
- package/dist/tools/createProduct.js +214 -0
- package/dist/tools/createProductOption.js +79 -0
- package/dist/tools/createRedirect.js +62 -0
- package/dist/tools/deleteCollection.js +55 -0
- package/dist/tools/deleteMetafield.js +91 -0
- package/dist/tools/deleteMetaobject.js +56 -0
- package/dist/tools/deleteProduct.js +61 -0
- package/dist/tools/deleteProductImages.js +96 -0
- package/dist/tools/deleteRedirect.js +57 -0
- package/dist/tools/deleteVariant.js +77 -0
- package/dist/tools/detachFileFromProduct.js +65 -0
- package/dist/tools/draftOrders.js +338 -0
- package/dist/tools/findProductsByMetafield.js +114 -0
- package/dist/tools/getBulkOperationResults.js +182 -0
- package/dist/tools/getBulkOperationStatus.js +133 -0
- package/dist/tools/getCollections.js +120 -0
- package/dist/tools/getCustomers.js +105 -0
- package/dist/tools/getFileUploadSession.js +46 -0
- package/dist/tools/getFiles.js +159 -0
- package/dist/tools/getInventoryLevels.js +152 -0
- package/dist/tools/getLocations.js +77 -0
- package/dist/tools/getMetafieldOptions.js +207 -0
- package/dist/tools/getMetafields.js +258 -0
- package/dist/tools/getMetaobject.js +73 -0
- package/dist/tools/getMetaobjectDefinition.js +76 -0
- package/dist/tools/getProductIssues.js +204 -0
- package/dist/tools/getRedirects.js +83 -0
- package/dist/tools/getStatus.js +95 -0
- package/dist/tools/getStoreCounts.js +115 -0
- package/dist/tools/listMetafieldDefinitions.js +121 -0
- package/dist/tools/listMetaobjectDefinitions.js +91 -0
- package/dist/tools/listMetaobjects.js +94 -0
- package/dist/tools/manageCollectionProducts.js +153 -0
- package/dist/tools/metaobjectDefinitionUtils.js +34 -0
- package/dist/tools/orders.js +335 -0
- package/dist/tools/products.js +417 -0
- package/dist/tools/reorderDraftProductMedia.js +148 -0
- package/dist/tools/searchTaxonomy.js +175 -0
- package/dist/tools/setMetafield.js +170 -0
- package/dist/tools/startBulkExport.js +255 -0
- package/dist/tools/updateCollection.js +162 -0
- package/dist/tools/updateCustomer.js +108 -0
- package/dist/tools/updateDraftOrder.js +251 -0
- package/dist/tools/updateInventory.js +192 -0
- package/dist/tools/updateInventoryItemCustoms.js +121 -0
- package/dist/tools/updateInventoryItemShipping.js +145 -0
- package/dist/tools/updateMetafieldDefinitionAccess.js +54 -0
- package/dist/tools/updateMetaobject.js +123 -0
- package/dist/tools/updateMetaobjectDefinition.js +150 -0
- package/dist/tools/updateOrder.js +132 -0
- package/dist/tools/updateProduct.js +546 -0
- package/dist/tools/uploadLocalFile.js +49 -0
- package/package.json +80 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Shopify MCP Server Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,412 @@
|
|
|
1
|
-
# Temporary Holding Version
|
|
2
1
|
|
|
3
|
-
|
|
2
|
+
```
|
|
3
|
+
███████╗██╗ ██╗ ██████╗ ██████╗ ██╗███████╗██╗ ██╗ ███╗ ███╗ ██████╗██████╗
|
|
4
|
+
██╔════╝██║ ██║██╔═══██╗██╔══██╗██║██╔════╝╚██╗ ██╔╝ ████╗ ████║██╔════╝██╔══██╗
|
|
5
|
+
███████╗███████║██║ ██║██████╔╝██║█████╗ ╚████╔╝ ██╔████╔██║██║ ██████╔╝
|
|
6
|
+
╚════██║██╔══██║██║ ██║██╔═══╝ ██║██╔══╝ ╚██╔╝ ██║╚██╔╝██║██║ ██╔═══╝
|
|
7
|
+
███████║██║ ██║╚██████╔╝██║ ██║██║ ██║ ██║ ╚═╝ ██║╚██████╗██║
|
|
8
|
+
╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
# Shopify MCP
|
|
12
|
+
|
|
13
|
+
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.
|
|
14
|
+
|
|
15
|
+
**npm:** `@benwmerritt/shopify-mcp`
|
|
16
|
+
**binary:** `shopify-mcp`
|
|
17
|
+
|
|
18
|
+
This project started from Ge Li's [shopify-mcp](https://github.com/GeLi2001/shopify-mcp). The unscoped `shopify-mcp` package on npm is theirs and does not include the changes in this repository.
|
|
19
|
+
|
|
20
|
+
## Highlights
|
|
21
|
+
|
|
22
|
+
- CRUD for products, collections, orders, and customers
|
|
23
|
+
- Draft orders for quotes, manual orders, and B2B pricing
|
|
24
|
+
- Inventory and location lookups for stock workflows
|
|
25
|
+
- Metafields for custom data
|
|
26
|
+
- Metaobject entry creation and lookup for existing definitions
|
|
27
|
+
- URL redirects management
|
|
28
|
+
- OAuth login flow with local token caching
|
|
29
|
+
- Bulk product cleanup utilities
|
|
30
|
+
- Fail-closed read-only mode for audit and QA agents
|
|
31
|
+
|
|
32
|
+
## Prerequisites
|
|
33
|
+
|
|
34
|
+
- Node.js 20+
|
|
35
|
+
- A Shopify custom app (OAuth or Admin API token)
|
|
36
|
+
|
|
37
|
+
## Local setup (this repo)
|
|
38
|
+
|
|
39
|
+
Use this when you want to run the MCP server from this local checkout instead of a remote deployment.
|
|
40
|
+
|
|
41
|
+
1. Install dependencies and build:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm install
|
|
45
|
+
npm run build
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
2. Create local env config:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
cp .env.example .env
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Set at least:
|
|
55
|
+
- `MYSHOPIFY_DOMAIN=your-store.myshopify.com`
|
|
56
|
+
- Either:
|
|
57
|
+
- `SHOPIFY_CLIENT_ID=...` and `SHOPIFY_CLIENT_SECRET=...` for a Dev Dashboard app owned by the store's organization; or
|
|
58
|
+
- `SHOPIFY_ACCESS_TOKEN=shpat_xxx` for a static/manual token
|
|
59
|
+
- `REMOTE_MCP=false`
|
|
60
|
+
|
|
61
|
+
3. Start local MCP (stdio):
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npm run start:local
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`start:local` uses stdio mode. Remote mode is only enabled with `--remote` or `REMOTE_MCP=true`.
|
|
68
|
+
|
|
69
|
+
### Read-only mode
|
|
70
|
+
|
|
71
|
+
Start a capability-restricted server for QA, audit, and reporting agents:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
shopify-mcp --read-only --domain=<YOUR_SHOP>.myshopify.com
|
|
75
|
+
# or
|
|
76
|
+
SHOPIFY_MCP_READ_ONLY=true npm run start:local
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Read-only mode exposes only a reviewed allowlist of lookup and reporting tools.
|
|
80
|
+
All mutating, mixed read/write, file-upload, and unknown future tools are hidden.
|
|
81
|
+
The allowlist is fail-closed, so a newly added tool does not appear in a
|
|
82
|
+
read-only instance until it is explicitly reviewed. `get-status` reports the
|
|
83
|
+
effective access mode, whether the boundary is enforced, and whether write
|
|
84
|
+
tools are exposed.
|
|
85
|
+
|
|
86
|
+
Use a least-privilege Shopify token as well when one is available. The server
|
|
87
|
+
boundary is designed to remain useful when a deployment must temporarily share
|
|
88
|
+
an existing token: agents connected to the read-only MCP instance cannot call
|
|
89
|
+
the hidden mutation tools.
|
|
90
|
+
|
|
91
|
+
## Install + run
|
|
92
|
+
|
|
93
|
+
### Client credentials (same Shopify organization)
|
|
94
|
+
|
|
95
|
+
For a Dev Dashboard app installed on a store owned by the same Shopify
|
|
96
|
+
organization, configure:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
MYSHOPIFY_DOMAIN=your-store.myshopify.com
|
|
100
|
+
SHOPIFY_CLIENT_ID=your-client-id
|
|
101
|
+
SHOPIFY_CLIENT_SECRET=your-client-secret
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
No callback URL or browser consent is required. The MCP obtains Shopify's
|
|
105
|
+
24-hour access token at startup, caches it per permanent MyShopify domain in
|
|
106
|
+
`~/.shopify-mcp/tokens.json`, and renews it automatically before expiry.
|
|
107
|
+
Cached tokens include the client ID so a token created by a different app is
|
|
108
|
+
never silently reused after an app cutover.
|
|
109
|
+
|
|
110
|
+
Shopify does not allow this grant for public or custom-distribution apps on
|
|
111
|
+
stores owned by another organization; use the authorization-code flow below
|
|
112
|
+
for those apps.
|
|
113
|
+
|
|
114
|
+
### Authorization-code OAuth
|
|
115
|
+
|
|
116
|
+
1. Create a custom app and copy **Client ID** and **Client Secret**.
|
|
117
|
+
2. In **App setup**, set **App URL** and **Allowed redirection URLs** to:
|
|
118
|
+
`http://localhost:3456/callback`
|
|
119
|
+
3. Start the OAuth flow:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
npx @benwmerritt/shopify-mcp --oauth --domain=your-store.myshopify.com --clientId=xxx --clientSecret=yyy
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Tokens are stored at `~/.shopify-mcp/tokens.json`. After that, start the server with just the domain:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
npx @benwmerritt/shopify-mcp --domain=your-store.myshopify.com
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Optional: override scopes with `--scopes` or `SHOPIFY_SCOPES`.
|
|
132
|
+
|
|
133
|
+
### Access token (manual)
|
|
134
|
+
|
|
135
|
+
1. Create a custom app in Shopify
|
|
136
|
+
2. Enable Admin API scopes:
|
|
137
|
+
- `read_products`, `write_products`
|
|
138
|
+
- `read_customers`, `write_customers`
|
|
139
|
+
- `read_orders`, `write_orders`
|
|
140
|
+
- `read_draft_orders`, `write_draft_orders`
|
|
141
|
+
- `read_inventory`, `write_inventory`
|
|
142
|
+
- `read_locations`
|
|
143
|
+
- `read_content`, `write_content`
|
|
144
|
+
- `read_files`, `write_files`
|
|
145
|
+
3. Install the app and copy the Admin API access token
|
|
146
|
+
|
|
147
|
+
Run:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
shopify-mcp --accessToken=<YOUR_ACCESS_TOKEN> --domain=<YOUR_SHOP>.myshopify.com
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## MCP client setup
|
|
154
|
+
|
|
155
|
+
### Claude Desktop (local repo build)
|
|
156
|
+
|
|
157
|
+
Build first (`npm run build`), then point Claude Desktop at this repo's built entrypoint:
|
|
158
|
+
|
|
159
|
+
```json
|
|
160
|
+
{
|
|
161
|
+
"mcpServers": {
|
|
162
|
+
"shopify-local": {
|
|
163
|
+
"command": "node",
|
|
164
|
+
"args": [
|
|
165
|
+
"/absolute/path/to/shopify-mcp/dist/index.js",
|
|
166
|
+
"--domain",
|
|
167
|
+
"your-store.myshopify.com"
|
|
168
|
+
],
|
|
169
|
+
"env": {
|
|
170
|
+
"SHOPIFY_ACCESS_TOKEN": "shpat_xxx",
|
|
171
|
+
"REMOTE_MCP": "false"
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
If you completed OAuth locally, remove `SHOPIFY_ACCESS_TOKEN` and keep `--domain`.
|
|
179
|
+
|
|
180
|
+
### Claude Desktop (npm package)
|
|
181
|
+
|
|
182
|
+
```json
|
|
183
|
+
{
|
|
184
|
+
"mcpServers": {
|
|
185
|
+
"shopify": {
|
|
186
|
+
"command": "npx",
|
|
187
|
+
"args": [
|
|
188
|
+
"@benwmerritt/shopify-mcp",
|
|
189
|
+
"--accessToken",
|
|
190
|
+
"<YOUR_ACCESS_TOKEN>",
|
|
191
|
+
"--domain",
|
|
192
|
+
"<YOUR_SHOP>.myshopify.com"
|
|
193
|
+
]
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
If you completed OAuth, omit `--accessToken` and keep `--domain`.
|
|
200
|
+
|
|
201
|
+
Config paths:
|
|
202
|
+
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
203
|
+
- Windows: `%APPDATA%/Claude/claude_desktop_config.json`
|
|
204
|
+
|
|
205
|
+
### Remote MCP (Railway, etc.)
|
|
206
|
+
|
|
207
|
+
By default this server runs as a local stdio MCP. Passing `--remote` (or setting
|
|
208
|
+
`REMOTE_MCP=true`) switches it to HTTP/SSE mode so it can be deployed as a remote
|
|
209
|
+
MCP server for Claude.ai or other remote clients. This repo ships a `Dockerfile`
|
|
210
|
+
and `railway.json` so Railway builds and starts it in remote mode out of the box.
|
|
211
|
+
|
|
212
|
+
**1. Get a token locally (one-time):**
|
|
213
|
+
```bash
|
|
214
|
+
npx @benwmerritt/shopify-mcp --oauth --domain=your-store.myshopify.com --clientId=xxx --clientSecret=yyy
|
|
215
|
+
# Token saved to ~/.shopify-mcp/tokens.json
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
**2. Deploy to Railway:**
|
|
219
|
+
- Create a project from this repo. Railway reads `railway.json` and builds the
|
|
220
|
+
`Dockerfile`, which starts the server with `--remote`.
|
|
221
|
+
- Set the service environment variables (Railway injects `PORT` automatically):
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
SHOPIFY_ACCESS_TOKEN=shpat_xxx # from tokens.json (or use the OAuth vars)
|
|
225
|
+
MYSHOPIFY_DOMAIN=your-store.myshopify.com
|
|
226
|
+
MCP_API_KEY=choose-a-long-random-string # required to authenticate remote clients
|
|
227
|
+
# REMOTE_MCP=true is already implied by the Dockerfile's --remote flag
|
|
228
|
+
# PORT is injected by Railway (defaults to 3000 when run locally)
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
**3. Connect:**
|
|
232
|
+
- Health check: `GET /health`
|
|
233
|
+
- Streamable HTTP: `/mcp`, using POST for requests, including MCP 2026-07-28.
|
|
234
|
+
- Existing SSE clients: `GET /mcp?apiKey=<MCP_API_KEY>`.
|
|
235
|
+
- Existing SSE messages: `POST /messages?apiKey=<MCP_API_KEY>`.
|
|
236
|
+
|
|
237
|
+
Send `Authorization: Bearer <MCP_API_KEY>` with Streamable HTTP requests.
|
|
238
|
+
The existing `apiKey` query parameter also works on both transports. When
|
|
239
|
+
`MCP_API_KEY` is configured, missing or invalid credentials receive `401` on
|
|
240
|
+
all MCP routes except allowed `OPTIONS` preflights, which do not require credentials.
|
|
241
|
+
An explicit Bearer header takes precedence over the query key.
|
|
242
|
+
Without `MCP_API_KEY`, development mode binds only to `127.0.0.1`.
|
|
243
|
+
External deployments, including containers that publish a port, must configure a
|
|
244
|
+
key to listen on all interfaces. The health endpoint remains unauthenticated.
|
|
245
|
+
Use HTTPS and configure a key for a public deployment. This shared-key scheme
|
|
246
|
+
is not an MCP OAuth authorization server.
|
|
247
|
+
|
|
248
|
+
MCP routes validate the actual `Host` header before Origin and authentication,
|
|
249
|
+
including requests without an `Origin` header and `OPTIONS` preflights. Allowed
|
|
250
|
+
hostnames are the configured public app hostname, `localhost`, `127.0.0.1`, and
|
|
251
|
+
`[::1]`, with an optional port. Add custom proxy or domain hostnames through
|
|
252
|
+
`MCP_ALLOWED_HOSTS`, comma-separated, such as `mcp.example.com,internal-proxy.example.com`.
|
|
253
|
+
Use hostnames without schemes or ports; matches are exact and case-insensitive.
|
|
254
|
+
`X-Forwarded-Host` is not trusted. Other or missing Host headers receive `403`.
|
|
255
|
+
Native clients without an `Origin` header work when their Host is allowed. Browser requests to
|
|
256
|
+
MCP routes must use the configured public app origin, the local server origin,
|
|
257
|
+
or an exact origin listed in `MCP_ALLOWED_ORIGINS`, comma-separated, such as
|
|
258
|
+
`https://client.example,https://another-client.example`. Other origins receive
|
|
259
|
+
`403`, including preflight requests. Public app URL resolution continues to use
|
|
260
|
+
`PUBLIC_BASE_URL`, `APP_URL`, or the Railway public URL/domain variables.
|
|
261
|
+
The upload pages keep their existing URLs and short-lived upload-session checks.
|
|
262
|
+
|
|
263
|
+
**Test the container locally before deploying:**
|
|
264
|
+
```bash
|
|
265
|
+
docker build -t shopify-mcp .
|
|
266
|
+
docker run -p 3000:3000 \
|
|
267
|
+
-e MYSHOPIFY_DOMAIN=your-store.myshopify.com \
|
|
268
|
+
-e SHOPIFY_ACCESS_TOKEN=shpat_xxx \
|
|
269
|
+
-e MCP_API_KEY=test \
|
|
270
|
+
shopify-mcp
|
|
271
|
+
# then in another shell: curl localhost:3000/health
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Environment variables (optional)
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
SHOPIFY_ACCESS_TOKEN=your_access_token
|
|
278
|
+
MYSHOPIFY_DOMAIN=your-store.myshopify.com
|
|
279
|
+
# Optional OAuth values:
|
|
280
|
+
# SHOPIFY_CLIENT_ID=your_client_id
|
|
281
|
+
# SHOPIFY_CLIENT_SECRET=your_client_secret
|
|
282
|
+
# SHOPIFY_SCOPES=comma,separated,scopes
|
|
283
|
+
# Hide every mutating or unreviewed tool (also available as `--read-only`):
|
|
284
|
+
# SHOPIFY_MCP_READ_ONLY=true
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
## Protocol compatibility and upgrade notes
|
|
288
|
+
|
|
289
|
+
The server uses TypeScript SDK v2 and Zod 4. Local stdio selects the protocol
|
|
290
|
+
from the opening message. Remote POST `/mcp` supports both MCP 2026-07-28
|
|
291
|
+
and older initialization-based Streamable HTTP clients. Modern requests use
|
|
292
|
+
`server/discover` and per-request metadata rather than an initialization session.
|
|
293
|
+
|
|
294
|
+
Existing stdio commands, Shopify credentials, token renewal, OAuth setup,
|
|
295
|
+
read-only flags, tool names, and legacy SSE connection URLs remain available.
|
|
296
|
+
The same tool registrations and fail-closed allowlist serve every transport.
|
|
297
|
+
|
|
298
|
+
Compatibility limits:
|
|
299
|
+
|
|
300
|
+
- Node 18 is no longer supported by SDK v2. Upgrade Node to 20+ before updating
|
|
301
|
+
this server, or retain the previous release. The Docker image already uses Node 20.
|
|
302
|
+
- Legacy SSE uses the SDK's frozen `server-legacy` compatibility package.
|
|
303
|
+
New integrations should use Streamable HTTP; the SSE bridge receives no new features.
|
|
304
|
+
- Legacy Streamable HTTP on POST `/mcp` is stateless, with no session ID or replay.
|
|
305
|
+
GET `/mcp` without protocol/session headers remains the original SSE endpoint.
|
|
306
|
+
Streamable HTTP GET and DELETE session operations are unsupported. A reconnect
|
|
307
|
+
to legacy SSE creates a new connection, as before.
|
|
308
|
+
- Browser clients from other origins must configure `MCP_ALLOWED_ORIGINS`.
|
|
309
|
+
API-key authentication remains separate from Shopify OAuth and does not provide
|
|
310
|
+
automatic MCP OAuth discovery or token issuance.
|
|
311
|
+
|
|
312
|
+
Run `npm test -- --runInBand` for the application suite and
|
|
313
|
+
`npm run test:connections` for built-server connection and security tests.
|
|
314
|
+
Connection tests use SDK v1.17.1 and v2.0.0, explicitly pin modern clients to
|
|
315
|
+
2026-07-28, compare tool catalogs, and mock Shopify calls without store access.
|
|
316
|
+
See [the research notes](docs/mcp-sdk-v2-research.md) for official sources and migration details.
|
|
317
|
+
|
|
318
|
+
## Tool catalog
|
|
319
|
+
|
|
320
|
+
### Products
|
|
321
|
+
- `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.
|
|
322
|
+
- `create-product`
|
|
323
|
+
- `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`. Also takes `cost` (cost per item, on the simple form or per entry in `variants`; the response includes `cost` only when one was written, since reading it needs `read_inventory`; without that scope the write still succeeds and the response carries a `warnings` entry instead) and `renameOption: {from, to}` to rename a product option in place without touching variant IDs (send it on its own; it cannot be rolled back if a later write fails).
|
|
324
|
+
- `delete-product`
|
|
325
|
+
- `delete-variant`
|
|
326
|
+
- `delete-product-images`
|
|
327
|
+
- `bulk-update-products`
|
|
328
|
+
- `bulk-delete-products`
|
|
329
|
+
- `count-products-by-tag`
|
|
330
|
+
- `find-products-by-metafield` — list products that have / don't have / both for a given `namespace.key`, paginated across the whole catalog via cursor
|
|
331
|
+
- `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
|
|
332
|
+
|
|
333
|
+
### Collections
|
|
334
|
+
- `get-collections`
|
|
335
|
+
- `manage-collection-products`
|
|
336
|
+
- `create-collection`
|
|
337
|
+
- `update-collection`
|
|
338
|
+
- `delete-collection`
|
|
339
|
+
|
|
340
|
+
### Customers
|
|
341
|
+
- `get-customers` (supports pagination via `cursor`)
|
|
342
|
+
- `update-customer`
|
|
343
|
+
|
|
344
|
+
### Orders
|
|
345
|
+
- `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`.
|
|
346
|
+
- `update-order`
|
|
347
|
+
|
|
348
|
+
### Draft Orders
|
|
349
|
+
- `draft-orders` — unified lookup/list. Pass `id` for a single draft order; omit `id` to list with filters (`status`, `query`, pagination via `cursor`).
|
|
350
|
+
- `create-draft-order`
|
|
351
|
+
- `update-draft-order`
|
|
352
|
+
- `complete-draft-order`
|
|
353
|
+
|
|
354
|
+
### Inventory
|
|
355
|
+
- `get-inventory-levels`
|
|
356
|
+
- `update-inventory`
|
|
357
|
+
|
|
358
|
+
### Locations
|
|
359
|
+
- `get-locations`
|
|
360
|
+
|
|
361
|
+
### Metafields
|
|
362
|
+
- `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`)
|
|
363
|
+
- `set-metafield` (create or update; supports `metaobject_reference` / `list.metaobject_reference`)
|
|
364
|
+
- `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.
|
|
365
|
+
- `delete-metafield`
|
|
366
|
+
- `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).
|
|
367
|
+
- `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)
|
|
368
|
+
|
|
369
|
+
### Metaobjects
|
|
370
|
+
- `list-metaobject-definitions`
|
|
371
|
+
- `get-metaobject-definition`
|
|
372
|
+
- `create-metaobject` — optional `status` (`ACTIVE`/`DRAFT`); defaults to Shopify's `DRAFT` for publishable definitions, pass `ACTIVE` to publish on create
|
|
373
|
+
- `update-metaobject` — edit fields on an existing entry (only provided keys change); optional `status` to publish (`ACTIVE`) or unpublish (`DRAFT`)
|
|
374
|
+
- `delete-metaobject`
|
|
375
|
+
- `list-metaobjects` — returns `status` per entry; optional `status` filter (applied client-side to the fetched page)
|
|
376
|
+
- `get-metaobject` — returns the entry's publish `status`
|
|
377
|
+
|
|
378
|
+
### Files
|
|
379
|
+
- `get-files` — list/search files in the store
|
|
380
|
+
- `attach-file-to-product` — attach an existing media file to a product
|
|
381
|
+
- `detach-file-from-product` — remove a media file from a product
|
|
382
|
+
- `create-file-upload-session` — start a browser upload session (**remote mode only**)
|
|
383
|
+
- `get-file-upload-session` — check an upload session (**remote mode only**)
|
|
384
|
+
|
|
385
|
+
### URL redirects
|
|
386
|
+
- `get-redirects`
|
|
387
|
+
- `create-redirect`
|
|
388
|
+
- `delete-redirect`
|
|
389
|
+
|
|
390
|
+
### Analytics
|
|
391
|
+
- `get-store-counts` - Get all key counts in one call (products, variants, orders, customers, collections)
|
|
392
|
+
- `get-product-issues` - Audit products for problems (zero inventory, low stock, missing images, zero price)
|
|
393
|
+
|
|
394
|
+
### Bulk Operations
|
|
395
|
+
- `start-bulk-export` - Start async bulk export (products, orders, customers, inventory, or custom query)
|
|
396
|
+
- `get-bulk-operation-status` - Check progress of bulk operation
|
|
397
|
+
- `get-bulk-operation-results` - Download and parse completed results (summary, sample, or full)
|
|
398
|
+
|
|
399
|
+
### Server
|
|
400
|
+
- `get-status` - Report MCP server status, configured store, and connection health
|
|
401
|
+
|
|
402
|
+
## Debugging
|
|
403
|
+
|
|
404
|
+
Tail Claude Desktop logs:
|
|
405
|
+
|
|
406
|
+
```bash
|
|
407
|
+
tail -n 20 -f ~/Library/Logs/Claude/mcp*.log
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
## License
|
|
411
|
+
|
|
412
|
+
MIT
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
function parsePositiveInt(value, fallback) {
|
|
2
|
+
if (!value) {
|
|
3
|
+
return fallback;
|
|
4
|
+
}
|
|
5
|
+
const parsed = Number.parseInt(value, 10);
|
|
6
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
7
|
+
return fallback;
|
|
8
|
+
}
|
|
9
|
+
return parsed;
|
|
10
|
+
}
|
|
11
|
+
function normalizeBaseUrl(value) {
|
|
12
|
+
if (!value) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
const trimmed = value.trim();
|
|
16
|
+
if (!trimmed) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) {
|
|
20
|
+
return trimmed.replace(/\/+$/, "");
|
|
21
|
+
}
|
|
22
|
+
return `https://${trimmed.replace(/\/+$/, "")}`;
|
|
23
|
+
}
|
|
24
|
+
export const SHOPIFY_API_VERSION = process.env.SHOPIFY_API_VERSION?.trim() || "2026-01";
|
|
25
|
+
export const SHOPIFY_FILE_UPLOAD_MAX_BYTES = parsePositiveInt(process.env.SHOPIFY_FILE_UPLOAD_MAX_BYTES, 26214400);
|
|
26
|
+
export const SHOPIFY_FILE_UPLOAD_SESSION_TTL_MINUTES = Math.min(parsePositiveInt(process.env.SHOPIFY_FILE_UPLOAD_SESSION_TTL_MINUTES, 15), 60);
|
|
27
|
+
export function getPublicAppUrl(port) {
|
|
28
|
+
const explicitUrl = normalizeBaseUrl(process.env.PUBLIC_BASE_URL);
|
|
29
|
+
if (explicitUrl) {
|
|
30
|
+
return explicitUrl;
|
|
31
|
+
}
|
|
32
|
+
const appUrl = normalizeBaseUrl(process.env.APP_URL);
|
|
33
|
+
if (appUrl) {
|
|
34
|
+
return appUrl;
|
|
35
|
+
}
|
|
36
|
+
const railwayUrl = normalizeBaseUrl(process.env.RAILWAY_PUBLIC_URL);
|
|
37
|
+
if (railwayUrl) {
|
|
38
|
+
return railwayUrl;
|
|
39
|
+
}
|
|
40
|
+
const railwayDomain = normalizeBaseUrl(process.env.RAILWAY_PUBLIC_DOMAIN);
|
|
41
|
+
if (railwayDomain) {
|
|
42
|
+
return railwayDomain;
|
|
43
|
+
}
|
|
44
|
+
const railwayStaticUrl = normalizeBaseUrl(process.env.RAILWAY_STATIC_URL);
|
|
45
|
+
if (railwayStaticUrl) {
|
|
46
|
+
return railwayStaticUrl;
|
|
47
|
+
}
|
|
48
|
+
return `http://localhost:${port}`;
|
|
49
|
+
}
|