@anton.andrusenko/shopify-mcp-admin 1.1.1 → 1.1.3
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/README.md +210 -2
- package/dist/index.js +25 -7
- package/dist/setup-wizard-PVLOC3DU.js +697 -0
- package/package.json +8 -9
package/README.md
CHANGED
|
@@ -40,9 +40,25 @@ npx @anton.andrusenko/shopify-mcp-admin
|
|
|
40
40
|
npm install -g @anton.andrusenko/shopify-mcp-admin
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
###
|
|
43
|
+
### Setup Wizard (Recommended)
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
The easiest way to get started is with the interactive setup wizard:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npx @anton.andrusenko/shopify-mcp-admin init
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The wizard will:
|
|
52
|
+
1. **Prompt for your store URL** and validate the format
|
|
53
|
+
2. **Choose authentication method** — Legacy token or OAuth 2.0
|
|
54
|
+
3. **Test the connection** — Verifies credentials before saving
|
|
55
|
+
4. **Select your AI client** — Claude Desktop, Cursor, Windsurf, VS Code, LibreChat, or OpenAI
|
|
56
|
+
5. **Configure tool loading** — All tools, role preset, or lazy loading
|
|
57
|
+
6. **Generate and save config** — Creates the appropriate config file for your client
|
|
58
|
+
|
|
59
|
+
### Manual Configuration
|
|
60
|
+
|
|
61
|
+
Alternatively, set environment variables directly:
|
|
46
62
|
|
|
47
63
|
```bash
|
|
48
64
|
export SHOPIFY_STORE_URL=your-store.myshopify.com
|
|
@@ -191,6 +207,198 @@ Quit and reopen Claude Desktop. You should see "shopify" in the MCP servers list
|
|
|
191
207
|
|
|
192
208
|
---
|
|
193
209
|
|
|
210
|
+
## 💬 LibreChat Integration
|
|
211
|
+
|
|
212
|
+
[LibreChat](https://www.librechat.ai/) is an open-source AI chat interface that supports MCP servers natively. This section provides a complete guide to set up LibreChat with the Shopify MCP server.
|
|
213
|
+
|
|
214
|
+
### Prerequisites
|
|
215
|
+
|
|
216
|
+
- **Docker Desktop** — [Download](https://www.docker.com/products/docker-desktop) and ensure it's running
|
|
217
|
+
- **Git** — For cloning repositories
|
|
218
|
+
- **LLM API Key** — OpenAI or Anthropic API key for the chat functionality
|
|
219
|
+
|
|
220
|
+
### Quick Setup (Recommended)
|
|
221
|
+
|
|
222
|
+
Use the included setup script for automated installation:
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
# Clone this repo (if you haven't already)
|
|
226
|
+
git clone https://github.com/AntonAndrusenko/shopify-mcp-admin.git
|
|
227
|
+
cd shopify-mcp-admin
|
|
228
|
+
|
|
229
|
+
# Run the setup script
|
|
230
|
+
./scripts/setup-librechat.sh
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
The script will:
|
|
234
|
+
1. Check Docker is installed and running
|
|
235
|
+
2. Clone LibreChat to `~/LibreChat`
|
|
236
|
+
3. Prompt for your Shopify credentials (store URL, access token or OAuth)
|
|
237
|
+
4. Create `librechat.yaml` with MCP server configuration
|
|
238
|
+
5. Create `docker-compose.override.yml` to mount the config
|
|
239
|
+
6. Pull Docker images and start all containers
|
|
240
|
+
7. Verify the Shopify MCP server loads successfully
|
|
241
|
+
|
|
242
|
+
**Script Options:**
|
|
243
|
+
```bash
|
|
244
|
+
# Install to a custom directory
|
|
245
|
+
./scripts/setup-librechat.sh --install-dir /path/to/librechat
|
|
246
|
+
|
|
247
|
+
# Use local development build instead of npm package
|
|
248
|
+
./scripts/setup-librechat.sh --use-local
|
|
249
|
+
|
|
250
|
+
# Show help
|
|
251
|
+
./scripts/setup-librechat.sh --help
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Manual Setup
|
|
255
|
+
|
|
256
|
+
<details>
|
|
257
|
+
<summary>Click to expand manual installation steps</summary>
|
|
258
|
+
|
|
259
|
+
#### Step 1: Clone and Configure LibreChat
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
# Clone LibreChat
|
|
263
|
+
git clone https://github.com/danny-avila/LibreChat.git
|
|
264
|
+
cd LibreChat
|
|
265
|
+
|
|
266
|
+
# Create environment file
|
|
267
|
+
cp .env.example .env
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
#### Step 2: Create MCP Server Configuration
|
|
271
|
+
|
|
272
|
+
Create `librechat.yaml` in the LibreChat directory:
|
|
273
|
+
|
|
274
|
+
```yaml
|
|
275
|
+
version: 1.2.1
|
|
276
|
+
|
|
277
|
+
mcpServers:
|
|
278
|
+
shopify:
|
|
279
|
+
type: stdio
|
|
280
|
+
command: npx
|
|
281
|
+
args:
|
|
282
|
+
- -y
|
|
283
|
+
- "@anton.andrusenko/shopify-mcp-admin"
|
|
284
|
+
env:
|
|
285
|
+
SHOPIFY_STORE_URL: "your-store.myshopify.com"
|
|
286
|
+
# Option 1: Access Token
|
|
287
|
+
SHOPIFY_ACCESS_TOKEN: "shpat_xxxxx"
|
|
288
|
+
# Option 2: OAuth (comment out ACCESS_TOKEN, uncomment these)
|
|
289
|
+
# SHOPIFY_CLIENT_ID: "your_client_id"
|
|
290
|
+
# SHOPIFY_CLIENT_SECRET: "your_client_secret"
|
|
291
|
+
LOG_LEVEL: "info"
|
|
292
|
+
serverInstructions: true
|
|
293
|
+
timeout: 30000
|
|
294
|
+
initTimeout: 15000
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
#### Step 3: Mount Configuration in Docker
|
|
298
|
+
|
|
299
|
+
Create `docker-compose.override.yml`:
|
|
300
|
+
|
|
301
|
+
```yaml
|
|
302
|
+
services:
|
|
303
|
+
api:
|
|
304
|
+
volumes:
|
|
305
|
+
- type: bind
|
|
306
|
+
source: ./librechat.yaml
|
|
307
|
+
target: /app/librechat.yaml
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
#### Step 4: Start LibreChat
|
|
311
|
+
|
|
312
|
+
```bash
|
|
313
|
+
# Pull the latest images
|
|
314
|
+
docker compose pull
|
|
315
|
+
|
|
316
|
+
# Start all services
|
|
317
|
+
docker compose up -d
|
|
318
|
+
|
|
319
|
+
# Verify containers are running
|
|
320
|
+
docker compose ps
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
#### Step 5: Verify MCP Server Loaded
|
|
324
|
+
|
|
325
|
+
```bash
|
|
326
|
+
# Check logs for MCP initialization
|
|
327
|
+
docker compose logs api | grep -i "mcp"
|
|
328
|
+
|
|
329
|
+
# You should see:
|
|
330
|
+
# [MCP][shopify] Initialized in: XXXXms
|
|
331
|
+
# MCP servers initialized successfully. Added 79 MCP tools.
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
</details>
|
|
335
|
+
|
|
336
|
+
### After Installation: Enable MCP in LibreChat
|
|
337
|
+
|
|
338
|
+
Once LibreChat is running, follow these steps to use the Shopify MCP server:
|
|
339
|
+
|
|
340
|
+
#### 1. Create an Account
|
|
341
|
+
- Open http://localhost:3080 in your browser
|
|
342
|
+
- Click "Sign up" and create an account
|
|
343
|
+
- Log in with your new credentials
|
|
344
|
+
|
|
345
|
+
#### 2. Add an LLM API Key
|
|
346
|
+
- Click your **profile avatar** (bottom-left)
|
|
347
|
+
- Select **Settings**
|
|
348
|
+
- Go to **User Provider Keys**
|
|
349
|
+
- Add your API key:
|
|
350
|
+
- **OpenAI**: Paste your `sk-...` key
|
|
351
|
+
- **Anthropic**: Paste your `sk-ant-...` key
|
|
352
|
+
- Click **Save**
|
|
353
|
+
|
|
354
|
+
#### 3. Select the Shopify MCP Server
|
|
355
|
+
- Start a **New Chat**
|
|
356
|
+
- Look for the **MCP Servers** dropdown in the chat input area (shows a plug icon)
|
|
357
|
+
- Click it and select **"shopify"**
|
|
358
|
+
- You should see a "shopify" badge appear, indicating the MCP server is active
|
|
359
|
+
|
|
360
|
+
#### 4. Test the Integration
|
|
361
|
+
Try these prompts to verify everything works:
|
|
362
|
+
|
|
363
|
+
```
|
|
364
|
+
"What's my store name and what plan am I on?"
|
|
365
|
+
"List my products"
|
|
366
|
+
"Show me products with low inventory"
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### Troubleshooting
|
|
370
|
+
|
|
371
|
+
| Issue | Solution |
|
|
372
|
+
|-------|----------|
|
|
373
|
+
| MCP server not appearing | Check logs: `docker compose logs api \| grep -i mcp` |
|
|
374
|
+
| "shopify" badge not visible | Refresh the page, or restart: `docker compose restart` |
|
|
375
|
+
| Tools not working | Verify Shopify credentials in `librechat.yaml` |
|
|
376
|
+
| Docker won't start | Ensure Docker Desktop is running |
|
|
377
|
+
| Permission denied on script | Run: `chmod +x ./scripts/setup-librechat.sh` |
|
|
378
|
+
|
|
379
|
+
### Useful Commands
|
|
380
|
+
|
|
381
|
+
```bash
|
|
382
|
+
# View LibreChat logs
|
|
383
|
+
cd ~/LibreChat && docker compose logs api -f
|
|
384
|
+
|
|
385
|
+
# View only MCP-related logs
|
|
386
|
+
cd ~/LibreChat && docker compose logs api | grep -i "mcp\|shopify"
|
|
387
|
+
|
|
388
|
+
# Restart LibreChat (after config changes)
|
|
389
|
+
cd ~/LibreChat && docker compose restart
|
|
390
|
+
|
|
391
|
+
# Stop LibreChat
|
|
392
|
+
cd ~/LibreChat && docker compose down
|
|
393
|
+
|
|
394
|
+
# Update LibreChat
|
|
395
|
+
cd ~/LibreChat && git pull && docker compose pull && docker compose up -d
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
> 📄 **Advanced Configuration:** See `librechat.yaml.example` for multi-store setups, role presets, and HTTP/SSE transport modes.
|
|
399
|
+
|
|
400
|
+
---
|
|
401
|
+
|
|
194
402
|
## 🌐 OpenAI/ChatGPT Integration
|
|
195
403
|
|
|
196
404
|
For OpenAI function calling or ChatGPT plugins, use HTTP transport mode.
|
package/dist/index.js
CHANGED
|
@@ -5675,7 +5675,7 @@ async function createMarket(input) {
|
|
|
5675
5675
|
};
|
|
5676
5676
|
}
|
|
5677
5677
|
if (input.enabled !== void 0) {
|
|
5678
|
-
graphqlInput.status = input.enabled ? "ACTIVE" : "
|
|
5678
|
+
graphqlInput.status = input.enabled ? "ACTIVE" : "DRAFT";
|
|
5679
5679
|
}
|
|
5680
5680
|
if (input.currencySettings) {
|
|
5681
5681
|
const currencyInput = {};
|
|
@@ -5723,7 +5723,7 @@ async function updateMarket(marketId, input) {
|
|
|
5723
5723
|
graphqlInput.handle = input.handle;
|
|
5724
5724
|
}
|
|
5725
5725
|
if (input.enabled !== void 0) {
|
|
5726
|
-
graphqlInput.status = input.enabled ? "ACTIVE" : "
|
|
5726
|
+
graphqlInput.status = input.enabled ? "ACTIVE" : "DRAFT";
|
|
5727
5727
|
}
|
|
5728
5728
|
if (input.countryCodesToAdd && input.countryCodesToAdd.length > 0) {
|
|
5729
5729
|
graphqlInput.conditions = graphqlInput.conditions || {};
|
|
@@ -5959,7 +5959,7 @@ var inputSchema6 = z8.object({
|
|
|
5959
5959
|
'Array of ISO 3166-1 alpha-2 country codes to include in this market. Example: ["CA"] for Canada, ["FR", "DE", "IT"] for multiple European countries'
|
|
5960
5960
|
),
|
|
5961
5961
|
enabled: z8.boolean().optional().default(false).describe(
|
|
5962
|
-
"Whether the market should be enabled (status: ACTIVE). Default: false (creates market as
|
|
5962
|
+
"Whether the market should be enabled (status: ACTIVE). Default: false (creates market as DRAFT). Set to true only when ready to make the market live. \u26A0\uFE0F ACTIVE markets are immediately visible to customers in those regions."
|
|
5963
5963
|
),
|
|
5964
5964
|
baseCurrency: z8.string().length(3).optional().describe(
|
|
5965
5965
|
'Base currency code for the market (ISO 4217). Example: "USD", "EUR", "CAD", "GBP". If not set, uses store default currency.'
|
|
@@ -6006,7 +6006,7 @@ function registerCreateMarketTool() {
|
|
|
6006
6006
|
{
|
|
6007
6007
|
name: "create-market",
|
|
6008
6008
|
title: "Create Market",
|
|
6009
|
-
description: 'Create a new market for regional targeting. Markets group one or more regions (countries) for localized shopping experiences. Provide a name (required) and optionally: handle, country codes, currency settings, and enabled status. \u26A0\uFE0F **Note:** Markets are created as
|
|
6009
|
+
description: 'Create a new market for regional targeting. Markets group one or more regions (countries) for localized shopping experiences. Provide a name (required) and optionally: handle, country codes, currency settings, and enabled status. \u26A0\uFE0F **Note:** Markets are created as DRAFT by default (enabled=false). Use update-market to activate when ready, or set enabled=true if you want it live immediately. **Currency:** Set baseCurrency (e.g., "CAD") and localCurrencies for multi-currency support. **Typical workflow:** create-market \u2192 create-web-presence (configure SEO URLs) \u2192 enable-shop-locale (add languages) \u2192 update-market (enable when ready). **Prerequisites:** None. Store must have Markets feature enabled on their plan. **Follow-ups:** get-market, update-market, create-web-presence.',
|
|
6010
6010
|
inputSchema: inputSchema6,
|
|
6011
6011
|
outputSchema: outputSchema6,
|
|
6012
6012
|
// AI Agent Optimization
|
|
@@ -9168,7 +9168,7 @@ var outputSchema22 = z24.object({
|
|
|
9168
9168
|
name: z24.string().describe("Market name (not shown to customers)"),
|
|
9169
9169
|
handle: z24.string().describe("URL handle"),
|
|
9170
9170
|
enabled: z24.boolean().describe("Whether the market is enabled"),
|
|
9171
|
-
status: z24.enum(["ACTIVE", "
|
|
9171
|
+
status: z24.enum(["ACTIVE", "DRAFT"]).describe("Market status"),
|
|
9172
9172
|
type: z24.string().describe("Market type (PRIMARY, SINGLE_COUNTRY, MULTI_COUNTRY, CONTINENT)"),
|
|
9173
9173
|
regions: z24.array(
|
|
9174
9174
|
z24.object({
|
|
@@ -9831,7 +9831,7 @@ var outputSchema30 = z32.object({
|
|
|
9831
9831
|
name: z32.string().describe("Market name (not shown to customers)"),
|
|
9832
9832
|
handle: z32.string().describe("URL handle"),
|
|
9833
9833
|
enabled: z32.boolean().describe("Whether the market is enabled"),
|
|
9834
|
-
status: z32.enum(["ACTIVE", "
|
|
9834
|
+
status: z32.enum(["ACTIVE", "DRAFT"]).describe("Market status"),
|
|
9835
9835
|
type: z32.string().describe("Market type (PRIMARY, SINGLE_COUNTRY, MULTI_COUNTRY, CONTINENT)"),
|
|
9836
9836
|
regions: z32.array(
|
|
9837
9837
|
z32.object({
|
|
@@ -14691,11 +14691,17 @@ function showHelp() {
|
|
|
14691
14691
|
|
|
14692
14692
|
Usage:
|
|
14693
14693
|
shopify-mcp-admin [options]
|
|
14694
|
+
shopify-mcp-admin init Interactive setup wizard
|
|
14694
14695
|
|
|
14695
14696
|
Options:
|
|
14696
14697
|
--help, -h Show this help message
|
|
14697
14698
|
--version, -v Show version number
|
|
14698
14699
|
|
|
14700
|
+
Commands:
|
|
14701
|
+
init, setup Run the interactive setup wizard
|
|
14702
|
+
Configures credentials and generates config files
|
|
14703
|
+
for Claude Desktop, Cursor, LibreChat, etc.
|
|
14704
|
+
|
|
14699
14705
|
Environment Variables:
|
|
14700
14706
|
SHOPIFY_STORE_URL (required) Your Shopify store domain
|
|
14701
14707
|
SHOPIFY_ACCESS_TOKEN (required) Admin API access token
|
|
@@ -14704,7 +14710,8 @@ Environment Variables:
|
|
|
14704
14710
|
DEBUG Enable debug logging (1 or true)
|
|
14705
14711
|
|
|
14706
14712
|
Examples:
|
|
14707
|
-
npx shopify-mcp-admin
|
|
14713
|
+
npx shopify-mcp-admin init # Setup wizard
|
|
14714
|
+
npx shopify-mcp-admin # Start server (requires env vars)
|
|
14708
14715
|
TRANSPORT=http PORT=3000 shopify-mcp-admin`);
|
|
14709
14716
|
process.exit(0);
|
|
14710
14717
|
}
|
|
@@ -14712,6 +14719,11 @@ function showVersion() {
|
|
|
14712
14719
|
console.log(getPackageVersion());
|
|
14713
14720
|
process.exit(0);
|
|
14714
14721
|
}
|
|
14722
|
+
async function runSetup() {
|
|
14723
|
+
const { runSetupWizard } = await import("./setup-wizard-PVLOC3DU.js");
|
|
14724
|
+
await runSetupWizard();
|
|
14725
|
+
process.exit(0);
|
|
14726
|
+
}
|
|
14715
14727
|
var args = process.argv.slice(2);
|
|
14716
14728
|
if (args.includes("--help") || args.includes("-h")) {
|
|
14717
14729
|
showHelp();
|
|
@@ -14719,6 +14731,12 @@ if (args.includes("--help") || args.includes("-h")) {
|
|
|
14719
14731
|
if (args.includes("--version") || args.includes("-v")) {
|
|
14720
14732
|
showVersion();
|
|
14721
14733
|
}
|
|
14734
|
+
if (args.includes("init") || args.includes("setup") || args.includes("--setup")) {
|
|
14735
|
+
runSetup().catch((error) => {
|
|
14736
|
+
console.error("Setup failed:", error);
|
|
14737
|
+
process.exit(1);
|
|
14738
|
+
});
|
|
14739
|
+
}
|
|
14722
14740
|
async function main() {
|
|
14723
14741
|
try {
|
|
14724
14742
|
const config = getConfig();
|
|
@@ -0,0 +1,697 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli/setup-wizard.ts
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
5
|
+
import { homedir } from "os";
|
|
6
|
+
import { dirname, join } from "path";
|
|
7
|
+
import { confirm, input, password, select } from "@inquirer/prompts";
|
|
8
|
+
var colors = {
|
|
9
|
+
reset: "\x1B[0m",
|
|
10
|
+
bold: "\x1B[1m",
|
|
11
|
+
dim: "\x1B[2m",
|
|
12
|
+
// Shopify-inspired colors
|
|
13
|
+
green: "\x1B[38;5;82m",
|
|
14
|
+
// Shopify green
|
|
15
|
+
darkGreen: "\x1B[38;5;34m",
|
|
16
|
+
cyan: "\x1B[38;5;87m",
|
|
17
|
+
yellow: "\x1B[38;5;226m",
|
|
18
|
+
red: "\x1B[38;5;196m",
|
|
19
|
+
magenta: "\x1B[38;5;207m",
|
|
20
|
+
blue: "\x1B[38;5;75m",
|
|
21
|
+
white: "\x1B[38;5;255m",
|
|
22
|
+
gray: "\x1B[38;5;245m"
|
|
23
|
+
};
|
|
24
|
+
var c = {
|
|
25
|
+
green: (s) => `${colors.green}${s}${colors.reset}`,
|
|
26
|
+
darkGreen: (s) => `${colors.darkGreen}${s}${colors.reset}`,
|
|
27
|
+
cyan: (s) => `${colors.cyan}${s}${colors.reset}`,
|
|
28
|
+
yellow: (s) => `${colors.yellow}${s}${colors.reset}`,
|
|
29
|
+
red: (s) => `${colors.red}${s}${colors.reset}`,
|
|
30
|
+
magenta: (s) => `${colors.magenta}${s}${colors.reset}`,
|
|
31
|
+
blue: (s) => `${colors.blue}${s}${colors.reset}`,
|
|
32
|
+
white: (s) => `${colors.white}${s}${colors.reset}`,
|
|
33
|
+
gray: (s) => `${colors.gray}${s}${colors.reset}`,
|
|
34
|
+
bold: (s) => `${colors.bold}${s}${colors.reset}`,
|
|
35
|
+
dim: (s) => `${colors.dim}${s}${colors.reset}`
|
|
36
|
+
};
|
|
37
|
+
function gradientLine(line) {
|
|
38
|
+
const gradientColors = [
|
|
39
|
+
"\x1B[38;5;82m",
|
|
40
|
+
// green
|
|
41
|
+
"\x1B[38;5;83m",
|
|
42
|
+
"\x1B[38;5;84m",
|
|
43
|
+
"\x1B[38;5;85m",
|
|
44
|
+
"\x1B[38;5;86m",
|
|
45
|
+
"\x1B[38;5;87m"
|
|
46
|
+
// cyan
|
|
47
|
+
];
|
|
48
|
+
let result = "";
|
|
49
|
+
const chars = [...line];
|
|
50
|
+
for (let i = 0; i < chars.length; i++) {
|
|
51
|
+
const colorIndex = Math.floor(i / chars.length * gradientColors.length);
|
|
52
|
+
result += `${gradientColors[colorIndex]}${chars[i]}`;
|
|
53
|
+
}
|
|
54
|
+
return result + colors.reset;
|
|
55
|
+
}
|
|
56
|
+
var BANNER = `
|
|
57
|
+
${c.green("\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E")}
|
|
58
|
+
${c.green("\u2502")} ${c.green("\u2502")}
|
|
59
|
+
${c.green("\u2502")} ${gradientLine("\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557")} ${c.green("\u2502")}
|
|
60
|
+
${c.green("\u2502")} ${gradientLine("\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u255A\u2588\u2588\u2557 \u2588\u2588\u2554\u255D")} ${c.green("\u2502")}
|
|
61
|
+
${c.green("\u2502")} ${gradientLine("\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u255A\u2588\u2588\u2588\u2588\u2554\u255D ")} ${c.green("\u2502")}
|
|
62
|
+
${c.green("\u2502")} ${gradientLine("\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u255A\u2588\u2588\u2554\u255D ")} ${c.green("\u2502")}
|
|
63
|
+
${c.green("\u2502")} ${gradientLine("\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 ")} ${c.green("\u2502")}
|
|
64
|
+
${c.green("\u2502")} ${gradientLine("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D ")} ${c.green("\u2502")}
|
|
65
|
+
${c.green("\u2502")} ${c.green("\u2502")}
|
|
66
|
+
${c.green("\u2502")} ${c.bold("MCP ADMIN SERVER")} ${c.green("\u2502")}
|
|
67
|
+
${c.green("\u2502")} ${c.green("\u2502")}
|
|
68
|
+
${c.green("\u2502")} ${c.cyan("\u{1F6CD}\uFE0F Connect AI Agents to Your Shopify Store")} ${c.green("\u2502")}
|
|
69
|
+
${c.green("\u2502")} ${c.green("\u2502")}
|
|
70
|
+
${c.green("\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F")}
|
|
71
|
+
`;
|
|
72
|
+
function validateStoreUrl(value) {
|
|
73
|
+
const trimmed = value.trim().toLowerCase();
|
|
74
|
+
const cleaned = trimmed.replace(/^https?:\/\//, "");
|
|
75
|
+
if (!cleaned) {
|
|
76
|
+
return "Store URL is required";
|
|
77
|
+
}
|
|
78
|
+
if (!cleaned.endsWith(".myshopify.com")) {
|
|
79
|
+
return "Must be a valid myshopify.com domain (e.g., your-store.myshopify.com)";
|
|
80
|
+
}
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
function validateAccessToken(value) {
|
|
84
|
+
const trimmed = value.trim();
|
|
85
|
+
if (!trimmed) {
|
|
86
|
+
return "Access token is required";
|
|
87
|
+
}
|
|
88
|
+
if (!trimmed.startsWith("shpat_")) {
|
|
89
|
+
return 'Access token should start with "shpat_"';
|
|
90
|
+
}
|
|
91
|
+
if (trimmed.length < 20) {
|
|
92
|
+
return "Access token seems too short";
|
|
93
|
+
}
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
function cleanStoreUrl(url) {
|
|
97
|
+
return url.trim().toLowerCase().replace(/^https?:\/\//, "");
|
|
98
|
+
}
|
|
99
|
+
var Spinner = class {
|
|
100
|
+
frames = ["\u25D0", "\u25D3", "\u25D1", "\u25D2"];
|
|
101
|
+
frameIndex = 0;
|
|
102
|
+
intervalId = null;
|
|
103
|
+
message;
|
|
104
|
+
constructor(message) {
|
|
105
|
+
this.message = message;
|
|
106
|
+
}
|
|
107
|
+
start() {
|
|
108
|
+
process.stdout.write("\x1B[?25l");
|
|
109
|
+
this.intervalId = setInterval(() => {
|
|
110
|
+
const frame = this.frames[this.frameIndex];
|
|
111
|
+
process.stdout.write(`\r${c.cyan(frame)} ${this.message}`);
|
|
112
|
+
this.frameIndex = (this.frameIndex + 1) % this.frames.length;
|
|
113
|
+
}, 100);
|
|
114
|
+
}
|
|
115
|
+
succeed(message) {
|
|
116
|
+
this.stop();
|
|
117
|
+
console.log(`\r${c.green("\u2714")} ${message}`);
|
|
118
|
+
}
|
|
119
|
+
fail(message) {
|
|
120
|
+
this.stop();
|
|
121
|
+
console.log(`\r${c.red("\u2716")} ${message}`);
|
|
122
|
+
}
|
|
123
|
+
stop() {
|
|
124
|
+
if (this.intervalId) {
|
|
125
|
+
clearInterval(this.intervalId);
|
|
126
|
+
this.intervalId = null;
|
|
127
|
+
}
|
|
128
|
+
process.stdout.write("\x1B[?25h");
|
|
129
|
+
process.stdout.write("\r\x1B[K");
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
async function testConnection(config) {
|
|
133
|
+
const spinner = new Spinner(`Verifying connection to ${config.storeUrl}...`);
|
|
134
|
+
spinner.start();
|
|
135
|
+
try {
|
|
136
|
+
const query = `{
|
|
137
|
+
shop {
|
|
138
|
+
name
|
|
139
|
+
plan { displayName }
|
|
140
|
+
email
|
|
141
|
+
}
|
|
142
|
+
}`;
|
|
143
|
+
let accessToken = config.accessToken;
|
|
144
|
+
if (config.authMethod === "oauth" && config.clientId && config.clientSecret) {
|
|
145
|
+
const tokenResponse = await fetch(`https://${config.storeUrl}/admin/oauth/access_token`, {
|
|
146
|
+
method: "POST",
|
|
147
|
+
headers: { "Content-Type": "application/json" },
|
|
148
|
+
body: JSON.stringify({
|
|
149
|
+
grant_type: "client_credentials",
|
|
150
|
+
client_id: config.clientId,
|
|
151
|
+
client_secret: config.clientSecret
|
|
152
|
+
})
|
|
153
|
+
});
|
|
154
|
+
if (!tokenResponse.ok) {
|
|
155
|
+
spinner.fail("Failed to authenticate with OAuth credentials");
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
const tokenData = await tokenResponse.json();
|
|
159
|
+
accessToken = tokenData.access_token;
|
|
160
|
+
}
|
|
161
|
+
const response = await fetch(`https://${config.storeUrl}/admin/api/2025-01/graphql.json`, {
|
|
162
|
+
method: "POST",
|
|
163
|
+
headers: {
|
|
164
|
+
"Content-Type": "application/json",
|
|
165
|
+
"X-Shopify-Access-Token": accessToken || ""
|
|
166
|
+
},
|
|
167
|
+
body: JSON.stringify({ query })
|
|
168
|
+
});
|
|
169
|
+
if (!response.ok) {
|
|
170
|
+
if (response.status === 401) {
|
|
171
|
+
spinner.fail("Authentication failed - check your access token");
|
|
172
|
+
} else if (response.status === 403) {
|
|
173
|
+
spinner.fail("Access denied - check API scopes");
|
|
174
|
+
} else {
|
|
175
|
+
spinner.fail(`Connection failed (HTTP ${response.status})`);
|
|
176
|
+
}
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
const data = await response.json();
|
|
180
|
+
if (data.errors) {
|
|
181
|
+
spinner.fail(`API error: ${data.errors[0]?.message || "Unknown error"}`);
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
if (!data.data) {
|
|
185
|
+
spinner.fail("Invalid response from Shopify API");
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
const storeInfo = {
|
|
189
|
+
name: data.data.shop.name,
|
|
190
|
+
plan: data.data.shop.plan.displayName,
|
|
191
|
+
email: data.data.shop.email
|
|
192
|
+
};
|
|
193
|
+
spinner.succeed(`Connected! Store: "${storeInfo.name}" (${storeInfo.plan})`);
|
|
194
|
+
return storeInfo;
|
|
195
|
+
} catch (error) {
|
|
196
|
+
spinner.fail(`Connection error: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
function buildEnvVars(config) {
|
|
201
|
+
const env = {
|
|
202
|
+
SHOPIFY_STORE_URL: config.storeUrl
|
|
203
|
+
};
|
|
204
|
+
if (config.authMethod === "token" && config.accessToken) {
|
|
205
|
+
env.SHOPIFY_ACCESS_TOKEN = config.accessToken;
|
|
206
|
+
} else {
|
|
207
|
+
env.SHOPIFY_CLIENT_ID = config.clientId || "";
|
|
208
|
+
env.SHOPIFY_CLIENT_SECRET = config.clientSecret || "";
|
|
209
|
+
}
|
|
210
|
+
if (config.lazyLoading) {
|
|
211
|
+
env.SHOPIFY_MCP_LAZY_LOADING = "true";
|
|
212
|
+
} else if (config.role) {
|
|
213
|
+
env.SHOPIFY_MCP_ROLE = config.role;
|
|
214
|
+
}
|
|
215
|
+
if (config.transport === "http") {
|
|
216
|
+
env.TRANSPORT = "http";
|
|
217
|
+
env.PORT = String(config.port || 3e3);
|
|
218
|
+
}
|
|
219
|
+
return env;
|
|
220
|
+
}
|
|
221
|
+
function generateJsonConfig(config) {
|
|
222
|
+
const env = buildEnvVars(config);
|
|
223
|
+
const mcpConfig = {
|
|
224
|
+
mcpServers: {
|
|
225
|
+
shopify: {
|
|
226
|
+
command: "npx",
|
|
227
|
+
args: ["-y", "@anton.andrusenko/shopify-mcp-admin"],
|
|
228
|
+
env
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
return JSON.stringify(mcpConfig, null, 2);
|
|
233
|
+
}
|
|
234
|
+
function generateLibreChatConfig(config) {
|
|
235
|
+
const env = buildEnvVars(config);
|
|
236
|
+
const envLines = Object.entries(env).map(([key, value]) => ` ${key}: "${value}"`).join("\n");
|
|
237
|
+
return `# LibreChat Configuration for Shopify MCP Admin
|
|
238
|
+
# Generated by shopify-mcp-admin setup wizard
|
|
239
|
+
# Docs: https://github.com/AntonAndrusenko/shopify-mcp-admin
|
|
240
|
+
|
|
241
|
+
version: 1.2.1
|
|
242
|
+
cache: true
|
|
243
|
+
|
|
244
|
+
interface:
|
|
245
|
+
customWelcome: 'Welcome to LibreChat with Shopify MCP! \u{1F6CD}\uFE0F'
|
|
246
|
+
|
|
247
|
+
mcpServers:
|
|
248
|
+
shopify:
|
|
249
|
+
type: ${config.transport === "http" ? "sse" : "stdio"}
|
|
250
|
+
${config.transport === "http" ? `url: http://localhost:${config.port || 3e3}/sse` : `command: npx
|
|
251
|
+
args:
|
|
252
|
+
- -y
|
|
253
|
+
- "@anton.andrusenko/shopify-mcp-admin"`}
|
|
254
|
+
env:
|
|
255
|
+
${envLines}
|
|
256
|
+
LOG_LEVEL: "info"
|
|
257
|
+
serverInstructions: true
|
|
258
|
+
timeout: 30000
|
|
259
|
+
initTimeout: 15000
|
|
260
|
+
`;
|
|
261
|
+
}
|
|
262
|
+
function generateEnvFile(config) {
|
|
263
|
+
const env = buildEnvVars(config);
|
|
264
|
+
let content = `# Shopify MCP Admin Configuration
|
|
265
|
+
# Generated by shopify-mcp-admin setup wizard
|
|
266
|
+
# Docs: https://github.com/AntonAndrusenko/shopify-mcp-admin
|
|
267
|
+
|
|
268
|
+
`;
|
|
269
|
+
for (const [key, value] of Object.entries(env)) {
|
|
270
|
+
content += `${key}=${value}
|
|
271
|
+
`;
|
|
272
|
+
}
|
|
273
|
+
return content;
|
|
274
|
+
}
|
|
275
|
+
function generateShellExport(config) {
|
|
276
|
+
const env = buildEnvVars(config);
|
|
277
|
+
let content = `# Shopify MCP Admin - Shell Environment
|
|
278
|
+
# Add to your ~/.bashrc, ~/.zshrc, or run: source <filename>
|
|
279
|
+
|
|
280
|
+
`;
|
|
281
|
+
for (const [key, value] of Object.entries(env)) {
|
|
282
|
+
content += `export ${key}="${value}"
|
|
283
|
+
`;
|
|
284
|
+
}
|
|
285
|
+
content += `
|
|
286
|
+
# Run the server with:
|
|
287
|
+
# npx @anton.andrusenko/shopify-mcp-admin
|
|
288
|
+
`;
|
|
289
|
+
return content;
|
|
290
|
+
}
|
|
291
|
+
function getClaudeDesktopConfigPath() {
|
|
292
|
+
const platform = process.platform;
|
|
293
|
+
if (platform === "darwin") {
|
|
294
|
+
return join(
|
|
295
|
+
homedir(),
|
|
296
|
+
"Library",
|
|
297
|
+
"Application Support",
|
|
298
|
+
"Claude",
|
|
299
|
+
"claude_desktop_config.json"
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
if (platform === "win32") {
|
|
303
|
+
return join(process.env.APPDATA || "", "Claude", "claude_desktop_config.json");
|
|
304
|
+
}
|
|
305
|
+
return join(homedir(), ".config", "claude", "claude_desktop_config.json");
|
|
306
|
+
}
|
|
307
|
+
function getCursorConfigPath() {
|
|
308
|
+
return join(process.cwd(), ".cursor", "mcp.json");
|
|
309
|
+
}
|
|
310
|
+
function getWindsurfConfigPath() {
|
|
311
|
+
const platform = process.platform;
|
|
312
|
+
if (platform === "darwin") {
|
|
313
|
+
return join(homedir(), ".codeium", "windsurf", "mcp_config.json");
|
|
314
|
+
}
|
|
315
|
+
if (platform === "win32") {
|
|
316
|
+
return join(process.env.APPDATA || "", "Codeium", "windsurf", "mcp_config.json");
|
|
317
|
+
}
|
|
318
|
+
return join(homedir(), ".codeium", "windsurf", "mcp_config.json");
|
|
319
|
+
}
|
|
320
|
+
function getVSCodeConfigPath() {
|
|
321
|
+
return join(process.cwd(), ".vscode", "mcp.json");
|
|
322
|
+
}
|
|
323
|
+
function ensureDirectoryExists(filePath) {
|
|
324
|
+
const dir = dirname(filePath);
|
|
325
|
+
if (!existsSync(dir)) {
|
|
326
|
+
mkdirSync(dir, { recursive: true });
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
function mergeJsonConfig(existingPath, newConfig) {
|
|
330
|
+
if (!existsSync(existingPath)) {
|
|
331
|
+
return newConfig;
|
|
332
|
+
}
|
|
333
|
+
try {
|
|
334
|
+
const existing = JSON.parse(readFileSync(existingPath, "utf-8"));
|
|
335
|
+
const toMerge = JSON.parse(newConfig);
|
|
336
|
+
existing.mcpServers = {
|
|
337
|
+
...existing.mcpServers,
|
|
338
|
+
...toMerge.mcpServers
|
|
339
|
+
};
|
|
340
|
+
return JSON.stringify(existing, null, 2);
|
|
341
|
+
} catch {
|
|
342
|
+
return newConfig;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
function printSuccessBox(client, configPath) {
|
|
346
|
+
const lines = ["", ` ${c.green("\u2728")} ${c.bold("You're all set!")}`, ""];
|
|
347
|
+
lines.push(` ${c.cyan("Next steps:")}`);
|
|
348
|
+
switch (client) {
|
|
349
|
+
case "claude-desktop":
|
|
350
|
+
lines.push(" 1. Restart Claude Desktop");
|
|
351
|
+
lines.push(` 2. Look for "shopify" in the MCP servers list`);
|
|
352
|
+
lines.push(` 3. Try: "List all products in my store"`);
|
|
353
|
+
break;
|
|
354
|
+
case "cursor":
|
|
355
|
+
lines.push(" 1. Restart Cursor");
|
|
356
|
+
lines.push(" 2. The Shopify MCP server will be available");
|
|
357
|
+
lines.push(` 3. Try: "List all products in my Shopify store"`);
|
|
358
|
+
break;
|
|
359
|
+
case "windsurf":
|
|
360
|
+
lines.push(" 1. Restart Windsurf");
|
|
361
|
+
lines.push(` 2. Look for "shopify" in the Cascade MCP list`);
|
|
362
|
+
lines.push(` 3. Try: "List all products in my store"`);
|
|
363
|
+
break;
|
|
364
|
+
case "vscode-copilot":
|
|
365
|
+
lines.push(" 1. Restart VS Code");
|
|
366
|
+
lines.push(` 2. Use Copilot Chat with "shopify" MCP`);
|
|
367
|
+
lines.push(" 3. Try: @shopify list products");
|
|
368
|
+
break;
|
|
369
|
+
case "librechat":
|
|
370
|
+
lines.push(" 1. Copy librechat.yaml to your LibreChat directory");
|
|
371
|
+
lines.push(" 2. Restart LibreChat: docker compose restart");
|
|
372
|
+
lines.push(` 3. Select "shopify" in the MCP servers dropdown`);
|
|
373
|
+
break;
|
|
374
|
+
case "openai-http":
|
|
375
|
+
lines.push(" 1. Start the server: npx @anton.andrusenko/shopify-mcp-admin");
|
|
376
|
+
lines.push(" 2. Server will listen on the configured port");
|
|
377
|
+
lines.push(" 3. Connect your OpenAI integration via HTTP");
|
|
378
|
+
break;
|
|
379
|
+
case "other":
|
|
380
|
+
lines.push(` 1. Source the env file: source ${configPath}`);
|
|
381
|
+
lines.push(" 2. Or copy variables to your shell profile");
|
|
382
|
+
lines.push(" 3. Run: npx @anton.andrusenko/shopify-mcp-admin");
|
|
383
|
+
break;
|
|
384
|
+
}
|
|
385
|
+
lines.push("");
|
|
386
|
+
lines.push(
|
|
387
|
+
` ${c.gray("\u{1F4DA} Docs:")} ${c.cyan("https://github.com/AntonAndrusenko/shopify-mcp-admin")}`
|
|
388
|
+
);
|
|
389
|
+
lines.push("");
|
|
390
|
+
const maxLength = 65;
|
|
391
|
+
const boxTop = c.green(`\u256D${"\u2500".repeat(maxLength)}\u256E`);
|
|
392
|
+
const boxBottom = c.green(`\u2570${"\u2500".repeat(maxLength)}\u256F`);
|
|
393
|
+
console.log("");
|
|
394
|
+
console.log(boxTop);
|
|
395
|
+
const ansiRegex = /\x1b\[[0-9;]*m/g;
|
|
396
|
+
for (const line of lines) {
|
|
397
|
+
const visibleLength = line.replace(ansiRegex, "").length;
|
|
398
|
+
const padding = maxLength - visibleLength;
|
|
399
|
+
console.log(`${c.green("\u2502")}${line}${" ".repeat(Math.max(0, padding))}${c.green("\u2502")}`);
|
|
400
|
+
}
|
|
401
|
+
console.log(boxBottom);
|
|
402
|
+
console.log("");
|
|
403
|
+
}
|
|
404
|
+
async function runSetupWizard() {
|
|
405
|
+
console.clear();
|
|
406
|
+
console.log(BANNER);
|
|
407
|
+
const config = {
|
|
408
|
+
storeUrl: "",
|
|
409
|
+
authMethod: "token",
|
|
410
|
+
transport: "stdio",
|
|
411
|
+
lazyLoading: false,
|
|
412
|
+
client: "claude-desktop"
|
|
413
|
+
};
|
|
414
|
+
try {
|
|
415
|
+
console.log("");
|
|
416
|
+
const storeUrlInput = await input({
|
|
417
|
+
message: "What is your Shopify store URL?",
|
|
418
|
+
default: "your-store.myshopify.com",
|
|
419
|
+
validate: validateStoreUrl,
|
|
420
|
+
transformer: (value) => cleanStoreUrl(value)
|
|
421
|
+
});
|
|
422
|
+
config.storeUrl = cleanStoreUrl(storeUrlInput);
|
|
423
|
+
console.log("");
|
|
424
|
+
config.authMethod = await select({
|
|
425
|
+
message: "How would you like to authenticate?",
|
|
426
|
+
choices: [
|
|
427
|
+
{
|
|
428
|
+
name: "Access Token (Legacy Custom App)",
|
|
429
|
+
value: "token",
|
|
430
|
+
description: "I have a shpat_xxx token from a Custom App"
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
name: "OAuth 2.0 (Dev Dashboard)",
|
|
434
|
+
value: "oauth",
|
|
435
|
+
description: "I have a Client ID and Client Secret"
|
|
436
|
+
}
|
|
437
|
+
]
|
|
438
|
+
});
|
|
439
|
+
console.log("");
|
|
440
|
+
if (config.authMethod === "token") {
|
|
441
|
+
config.accessToken = await password({
|
|
442
|
+
message: "Paste your access token (shpat_xxx)",
|
|
443
|
+
mask: "\u2022",
|
|
444
|
+
validate: validateAccessToken
|
|
445
|
+
});
|
|
446
|
+
} else {
|
|
447
|
+
config.clientId = await input({
|
|
448
|
+
message: "Enter your Client ID",
|
|
449
|
+
validate: (v) => v.trim().length > 0 || "Client ID is required"
|
|
450
|
+
});
|
|
451
|
+
config.clientSecret = await password({
|
|
452
|
+
message: "Enter your Client Secret",
|
|
453
|
+
mask: "\u2022",
|
|
454
|
+
validate: (v) => v.trim().length > 0 || "Client Secret is required"
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
console.log("");
|
|
458
|
+
const storeInfo = await testConnection(config);
|
|
459
|
+
if (!storeInfo) {
|
|
460
|
+
const retry = await confirm({
|
|
461
|
+
message: "Connection failed. Would you like to try different credentials?",
|
|
462
|
+
default: true
|
|
463
|
+
});
|
|
464
|
+
if (retry) {
|
|
465
|
+
return runSetupWizard();
|
|
466
|
+
}
|
|
467
|
+
console.log(c.yellow("\nSetup cancelled. Please check your credentials and try again."));
|
|
468
|
+
process.exit(1);
|
|
469
|
+
}
|
|
470
|
+
console.log("");
|
|
471
|
+
config.client = await select({
|
|
472
|
+
message: "Which AI client will you use?",
|
|
473
|
+
choices: [
|
|
474
|
+
{
|
|
475
|
+
name: "Claude Desktop",
|
|
476
|
+
value: "claude-desktop",
|
|
477
|
+
description: "Anthropic's official Claude app"
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
name: "Cursor",
|
|
481
|
+
value: "cursor",
|
|
482
|
+
description: "AI-powered code editor"
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
name: "Windsurf",
|
|
486
|
+
value: "windsurf",
|
|
487
|
+
description: "Codeium AI IDE with Cascade"
|
|
488
|
+
},
|
|
489
|
+
{
|
|
490
|
+
name: "VS Code (Copilot)",
|
|
491
|
+
value: "vscode-copilot",
|
|
492
|
+
description: "VS Code with GitHub Copilot MCP"
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
name: "LibreChat",
|
|
496
|
+
value: "librechat",
|
|
497
|
+
description: "Open-source chat UI (Docker)"
|
|
498
|
+
},
|
|
499
|
+
{
|
|
500
|
+
name: "OpenAI / HTTP Integration",
|
|
501
|
+
value: "openai-http",
|
|
502
|
+
description: "HTTP server for OpenAI function calling"
|
|
503
|
+
},
|
|
504
|
+
{
|
|
505
|
+
name: "Other / Manual Setup",
|
|
506
|
+
value: "other",
|
|
507
|
+
description: "Generate shell exports for manual configuration"
|
|
508
|
+
}
|
|
509
|
+
]
|
|
510
|
+
});
|
|
511
|
+
console.log("");
|
|
512
|
+
const loadingStrategy = await select({
|
|
513
|
+
message: "How would you like to load tools?",
|
|
514
|
+
choices: [
|
|
515
|
+
{
|
|
516
|
+
name: `Load all tools at startup ${c.dim("(recommended)")}`,
|
|
517
|
+
value: "all",
|
|
518
|
+
description: "All 79 tools immediately available - best for most users"
|
|
519
|
+
},
|
|
520
|
+
{
|
|
521
|
+
name: `Load by role preset ${c.dim("(optimized)")}`,
|
|
522
|
+
value: "role",
|
|
523
|
+
description: "Load tools based on your workflow - reduces AI token usage"
|
|
524
|
+
},
|
|
525
|
+
{
|
|
526
|
+
name: `Lazy loading ${c.dim("(on-demand)")}`,
|
|
527
|
+
value: "lazy",
|
|
528
|
+
description: '15 core tools at start, load more with "load-module" command'
|
|
529
|
+
}
|
|
530
|
+
]
|
|
531
|
+
});
|
|
532
|
+
if (loadingStrategy === "lazy") {
|
|
533
|
+
config.lazyLoading = true;
|
|
534
|
+
console.log(
|
|
535
|
+
c.gray(' \u2139\uFE0F Lazy loading: Use "list-modules" and "load-module" tools to add more tools')
|
|
536
|
+
);
|
|
537
|
+
} else if (loadingStrategy === "role") {
|
|
538
|
+
console.log("");
|
|
539
|
+
const roleChoice = await select({
|
|
540
|
+
message: "Select your role preset:",
|
|
541
|
+
choices: [
|
|
542
|
+
{
|
|
543
|
+
name: `Product Manager ${c.dim("(41 tools)")}`,
|
|
544
|
+
value: "product-manager",
|
|
545
|
+
description: "Products, inventory, collections, and metafields"
|
|
546
|
+
},
|
|
547
|
+
{
|
|
548
|
+
name: `Content Manager ${c.dim("(37 tools)")}`,
|
|
549
|
+
value: "content-manager",
|
|
550
|
+
description: "Pages, blogs, articles, and SEO content"
|
|
551
|
+
},
|
|
552
|
+
{
|
|
553
|
+
name: `International Manager ${c.dim("(46 tools)")}`,
|
|
554
|
+
value: "international-manager",
|
|
555
|
+
description: "Markets, locales, and translations"
|
|
556
|
+
},
|
|
557
|
+
{
|
|
558
|
+
name: `SEO Specialist ${c.dim("(38 tools)")}`,
|
|
559
|
+
value: "seo-specialist",
|
|
560
|
+
description: "SEO optimization, redirects, and URL management"
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
name: `Inventory Manager ${c.dim("(15 tools)")}`,
|
|
564
|
+
value: "inventory-manager",
|
|
565
|
+
description: "Core product and inventory tools only"
|
|
566
|
+
}
|
|
567
|
+
]
|
|
568
|
+
});
|
|
569
|
+
config.role = roleChoice;
|
|
570
|
+
}
|
|
571
|
+
if (config.client === "librechat" || config.client === "openai-http" || config.client === "other") {
|
|
572
|
+
console.log("");
|
|
573
|
+
config.transport = await select({
|
|
574
|
+
message: "Which transport mode?",
|
|
575
|
+
default: config.client === "openai-http" ? "http" : "stdio",
|
|
576
|
+
choices: [
|
|
577
|
+
{
|
|
578
|
+
name: `STDIO ${c.dim("(recommended for most clients)")}`,
|
|
579
|
+
value: "stdio",
|
|
580
|
+
description: "Standard input/output communication"
|
|
581
|
+
},
|
|
582
|
+
{
|
|
583
|
+
name: `HTTP ${c.dim("(for web integrations)")}`,
|
|
584
|
+
value: "http",
|
|
585
|
+
description: "HTTP server with SSE support"
|
|
586
|
+
}
|
|
587
|
+
]
|
|
588
|
+
});
|
|
589
|
+
if (config.transport === "http") {
|
|
590
|
+
const portInput = await input({
|
|
591
|
+
message: "HTTP server port",
|
|
592
|
+
default: "3000",
|
|
593
|
+
validate: (v) => {
|
|
594
|
+
const n = Number.parseInt(v, 10);
|
|
595
|
+
return n > 0 && n < 65536 || "Must be a valid port number";
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
config.port = Number.parseInt(portInput, 10);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
if (config.client === "openai-http") {
|
|
602
|
+
config.transport = "http";
|
|
603
|
+
config.port = config.port || 3e3;
|
|
604
|
+
}
|
|
605
|
+
console.log("");
|
|
606
|
+
let configContent;
|
|
607
|
+
let configPath;
|
|
608
|
+
let configDescription;
|
|
609
|
+
switch (config.client) {
|
|
610
|
+
case "claude-desktop": {
|
|
611
|
+
configPath = getClaudeDesktopConfigPath();
|
|
612
|
+
configContent = generateJsonConfig(config);
|
|
613
|
+
configContent = mergeJsonConfig(configPath, configContent);
|
|
614
|
+
configDescription = "Claude Desktop config";
|
|
615
|
+
break;
|
|
616
|
+
}
|
|
617
|
+
case "cursor": {
|
|
618
|
+
configPath = getCursorConfigPath();
|
|
619
|
+
configContent = generateJsonConfig(config);
|
|
620
|
+
configContent = mergeJsonConfig(configPath, configContent);
|
|
621
|
+
configDescription = "Cursor MCP config";
|
|
622
|
+
break;
|
|
623
|
+
}
|
|
624
|
+
case "windsurf": {
|
|
625
|
+
configPath = getWindsurfConfigPath();
|
|
626
|
+
configContent = generateJsonConfig(config);
|
|
627
|
+
configContent = mergeJsonConfig(configPath, configContent);
|
|
628
|
+
configDescription = "Windsurf MCP config";
|
|
629
|
+
break;
|
|
630
|
+
}
|
|
631
|
+
case "vscode-copilot": {
|
|
632
|
+
configPath = getVSCodeConfigPath();
|
|
633
|
+
configContent = generateJsonConfig(config);
|
|
634
|
+
configContent = mergeJsonConfig(configPath, configContent);
|
|
635
|
+
configDescription = "VS Code MCP config";
|
|
636
|
+
break;
|
|
637
|
+
}
|
|
638
|
+
case "librechat": {
|
|
639
|
+
configPath = join(process.cwd(), "librechat.yaml");
|
|
640
|
+
configContent = generateLibreChatConfig(config);
|
|
641
|
+
configDescription = "LibreChat config";
|
|
642
|
+
break;
|
|
643
|
+
}
|
|
644
|
+
case "openai-http": {
|
|
645
|
+
configPath = join(process.cwd(), ".env.shopify-mcp");
|
|
646
|
+
configContent = generateEnvFile(config);
|
|
647
|
+
configDescription = "Environment file (HTTP mode)";
|
|
648
|
+
break;
|
|
649
|
+
}
|
|
650
|
+
default: {
|
|
651
|
+
configPath = join(process.cwd(), ".shopify-mcp.env");
|
|
652
|
+
configContent = generateShellExport(config);
|
|
653
|
+
configDescription = "Shell environment exports";
|
|
654
|
+
break;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
console.log(c.cyan(`
|
|
658
|
+
\u{1F4C4} ${configDescription} preview:
|
|
659
|
+
`));
|
|
660
|
+
console.log(c.gray("\u2500".repeat(60)));
|
|
661
|
+
console.log(c.dim(configContent));
|
|
662
|
+
console.log(c.gray("\u2500".repeat(60)));
|
|
663
|
+
const shouldSave = await confirm({
|
|
664
|
+
message: `Save to ${configPath}?`,
|
|
665
|
+
default: true
|
|
666
|
+
});
|
|
667
|
+
if (shouldSave) {
|
|
668
|
+
ensureDirectoryExists(configPath);
|
|
669
|
+
writeFileSync(configPath, configContent, "utf-8");
|
|
670
|
+
console.log(`
|
|
671
|
+
${c.green("\u2714")} Configuration saved to: ${c.cyan(configPath)}`);
|
|
672
|
+
} else {
|
|
673
|
+
console.log(`
|
|
674
|
+
${c.yellow("\u2139")} Configuration not saved. You can copy the preview above.`);
|
|
675
|
+
}
|
|
676
|
+
printSuccessBox(config.client, configPath);
|
|
677
|
+
} catch (error) {
|
|
678
|
+
if (error instanceof Error && error.name === "ExitPromptError") {
|
|
679
|
+
console.log(c.yellow("\n\nSetup cancelled."));
|
|
680
|
+
process.exit(0);
|
|
681
|
+
}
|
|
682
|
+
throw error;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
function isSetupCommand(args) {
|
|
686
|
+
return args.includes("init") || args.includes("setup") || args.includes("--setup");
|
|
687
|
+
}
|
|
688
|
+
if (process.argv[1]?.includes("setup-wizard")) {
|
|
689
|
+
runSetupWizard().catch((error) => {
|
|
690
|
+
console.error(c.red("Setup failed:"), error);
|
|
691
|
+
process.exit(1);
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
export {
|
|
695
|
+
isSetupCommand,
|
|
696
|
+
runSetupWizard
|
|
697
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@anton.andrusenko/shopify-mcp-admin",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.3",
|
|
4
4
|
"description": "MCP server for Shopify Admin API - enables AI agents to manage Shopify stores with 79 tools for products, inventory, collections, content, SEO, metafields, markets & translations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"sideEffects": false,
|
|
12
12
|
"scripts": {
|
|
13
13
|
"dev": "tsx src/index.ts",
|
|
14
|
-
"build": "tsup src/index.ts --format esm --dts",
|
|
14
|
+
"build": "tsup src/index.ts --format esm --dts --clean",
|
|
15
15
|
"start": "node dist/index.js",
|
|
16
16
|
"lint": "biome check src/",
|
|
17
17
|
"lint:fix": "biome check --fix src/",
|
|
@@ -24,16 +24,14 @@
|
|
|
24
24
|
"inspect": "npx @modelcontextprotocol/inspector node dist/index.js",
|
|
25
25
|
"inspect:dev": "npx @modelcontextprotocol/inspector npx tsx src/index.ts",
|
|
26
26
|
"inspect:config": "npx @modelcontextprotocol/inspector --config mcp-inspector.json",
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
27
|
+
"wizard": "npx tsx src/index.ts init",
|
|
28
|
+
"wizard:build": "node dist/index.js init",
|
|
29
|
+
"librechat:setup": "./scripts/setup-librechat.sh",
|
|
30
|
+
"librechat:setup:local": "./scripts/setup-librechat.sh --use-local",
|
|
31
31
|
"ci": "npm run lint && npm run typecheck && npm run test && npm run build",
|
|
32
32
|
"version:patch": "npm version patch --no-git-tag-version",
|
|
33
33
|
"version:minor": "npm version minor --no-git-tag-version",
|
|
34
|
-
"version:major": "npm version major --no-git-tag-version"
|
|
35
|
-
"release": "npm run ci && npm publish --access public",
|
|
36
|
-
"prepublishOnly": "npm run ci"
|
|
34
|
+
"version:major": "npm version major --no-git-tag-version"
|
|
37
35
|
},
|
|
38
36
|
"keywords": [
|
|
39
37
|
"shopify",
|
|
@@ -66,6 +64,7 @@
|
|
|
66
64
|
"LICENSE"
|
|
67
65
|
],
|
|
68
66
|
"dependencies": {
|
|
67
|
+
"@inquirer/prompts": "^7.5.1",
|
|
69
68
|
"@modelcontextprotocol/sdk": "^1.22.0",
|
|
70
69
|
"@shopify/shopify-api": "^11.14.1",
|
|
71
70
|
"express": "^5.1.0",
|