shopify-store-mcp 1.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 +7 -0
- package/README.md +172 -0
- package/dist/config.d.ts +10 -0
- package/dist/config.js +65 -0
- package/dist/db.d.ts +19 -0
- package/dist/db.js +161 -0
- package/dist/errors.d.ts +36 -0
- package/dist/errors.js +93 -0
- package/dist/graphql/admin/common/collections.d.ts +8 -0
- package/dist/graphql/admin/common/collections.js +44 -0
- package/dist/graphql/admin/common/customers.d.ts +13 -0
- package/dist/graphql/admin/common/customers.js +112 -0
- package/dist/graphql/admin/common/orders.d.ts +13 -0
- package/dist/graphql/admin/common/orders.js +142 -0
- package/dist/graphql/admin/common/products.d.ts +23 -0
- package/dist/graphql/admin/common/products.js +159 -0
- package/dist/graphql/admin/common/shop.d.ts +7 -0
- package/dist/graphql/admin/common/shop.js +38 -0
- package/dist/graphql/admin/index.d.ts +15 -0
- package/dist/graphql/admin/index.js +18 -0
- package/dist/graphql/admin/specialized/bulk.d.ts +33 -0
- package/dist/graphql/admin/specialized/bulk.js +132 -0
- package/dist/graphql/admin/specialized/files.d.ts +22 -0
- package/dist/graphql/admin/specialized/files.js +170 -0
- package/dist/graphql/admin/specialized/inventory.d.ts +13 -0
- package/dist/graphql/admin/specialized/inventory.js +78 -0
- package/dist/graphql/admin/specialized/metafields.d.ts +22 -0
- package/dist/graphql/admin/specialized/metafields.js +100 -0
- package/dist/graphql/admin/specialized/metaobjects.d.ts +36 -0
- package/dist/graphql/admin/specialized/metaobjects.js +239 -0
- package/dist/graphql/admin/specialized/search.d.ts +21 -0
- package/dist/graphql/admin/specialized/search.js +100 -0
- package/dist/graphql/collections.d.ts +1 -0
- package/dist/graphql/collections.js +37 -0
- package/dist/graphql/customers.d.ts +2 -0
- package/dist/graphql/customers.js +98 -0
- package/dist/graphql/inventory.d.ts +2 -0
- package/dist/graphql/inventory.js +67 -0
- package/dist/graphql/metafields.d.ts +2 -0
- package/dist/graphql/metafields.js +43 -0
- package/dist/graphql/orders.d.ts +2 -0
- package/dist/graphql/orders.js +116 -0
- package/dist/graphql/products.d.ts +4 -0
- package/dist/graphql/products.js +140 -0
- package/dist/graphql/shop.d.ts +1 -0
- package/dist/graphql/shop.js +32 -0
- package/dist/graphql/storefront/common/cart.d.ts +23 -0
- package/dist/graphql/storefront/common/cart.js +210 -0
- package/dist/graphql/storefront/common/collections.d.ts +11 -0
- package/dist/graphql/storefront/common/collections.js +114 -0
- package/dist/graphql/storefront/common/products.d.ts +14 -0
- package/dist/graphql/storefront/common/products.js +155 -0
- package/dist/graphql/storefront/index.d.ts +7 -0
- package/dist/graphql/storefront/index.js +8 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +97 -0
- package/dist/logger.d.ts +58 -0
- package/dist/logger.js +165 -0
- package/dist/prompts/index.d.ts +2 -0
- package/dist/prompts/index.js +169 -0
- package/dist/queue.d.ts +73 -0
- package/dist/queue.js +120 -0
- package/dist/resources/index.d.ts +3 -0
- package/dist/resources/index.js +180 -0
- package/dist/shopify-client.d.ts +16 -0
- package/dist/shopify-client.js +39 -0
- package/dist/tools/graphql.d.ts +3 -0
- package/dist/tools/graphql.js +41 -0
- package/dist/tools/index.d.ts +3 -0
- package/dist/tools/index.js +23 -0
- package/dist/tools/infrastructure.d.ts +6 -0
- package/dist/tools/infrastructure.js +215 -0
- package/dist/tools/shop.d.ts +3 -0
- package/dist/tools/shop.js +28 -0
- package/dist/tools/smart-bulk.d.ts +7 -0
- package/dist/tools/smart-bulk.js +286 -0
- package/dist/tools/smart-files.d.ts +7 -0
- package/dist/tools/smart-files.js +169 -0
- package/dist/tools/smart-metaobjects.d.ts +7 -0
- package/dist/tools/smart-metaobjects.js +186 -0
- package/dist/tools/smart-schema.d.ts +7 -0
- package/dist/tools/smart-schema.js +138 -0
- package/dist/types.d.ts +19 -0
- package/dist/types.js +2 -0
- package/dist/utils/polling.d.ts +53 -0
- package/dist/utils/polling.js +77 -0
- package/package.json +83 -0
- package/prisma/schema.prisma +82 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright 2025-present, Brahim Benzarti
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# Shopify Store MCP Server
|
|
2
|
+
|
|
3
|
+
A Model Context Protocol (MCP) server that connects to **live Shopify stores** via the Admin and Storefront APIs. Unlike documentation-only MCPs, this server enables AI agents to perform real operations on your store.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Universal GraphQL Access** - Execute any Admin API query or mutation via `run_graphql_query`
|
|
8
|
+
- **Smart Multi-Step Tools** - File uploads, bulk operations, metaobject upserts with automatic polling
|
|
9
|
+
- **Rate Limiting** - Respects Shopify's plan-based rate limits (Standard/Advanced/Plus/Enterprise)
|
|
10
|
+
- **Operation Logging** - SQLite database tracks all operations for debugging and history
|
|
11
|
+
- **Schema Discovery** - Explore your store's metafield definitions and metaobject types
|
|
12
|
+
|
|
13
|
+
## Setup
|
|
14
|
+
|
|
15
|
+
### Prerequisites
|
|
16
|
+
|
|
17
|
+
1. A Shopify store with a [custom app](https://help.shopify.com/en/manual/apps/app-types/custom-apps)
|
|
18
|
+
2. Admin API access token with required scopes
|
|
19
|
+
3. Node.js 18+
|
|
20
|
+
|
|
21
|
+
### Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install -g shopify-store-mcp
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Or run directly with npx:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx shopify-store-mcp
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Usage with Claude Desktop or Cursor
|
|
34
|
+
|
|
35
|
+
Add the following to your MCP configuration:
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"mcpServers": {
|
|
40
|
+
"shopify-store": {
|
|
41
|
+
"command": "npx",
|
|
42
|
+
"args": ["-y", "shopify-store-mcp"],
|
|
43
|
+
"env": {
|
|
44
|
+
"SHOPIFY_STORE_URL": "your-store.myshopify.com",
|
|
45
|
+
"SHOPIFY_ACCESS_TOKEN": "shpat_xxxxxxxxxxxxxxxxxxxxx"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
On Windows, use this configuration:
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"mcpServers": {
|
|
57
|
+
"shopify-store": {
|
|
58
|
+
"command": "cmd",
|
|
59
|
+
"args": ["/k", "npx", "-y", "shopify-store-mcp"],
|
|
60
|
+
"env": {
|
|
61
|
+
"SHOPIFY_STORE_URL": "your-store.myshopify.com",
|
|
62
|
+
"SHOPIFY_ACCESS_TOKEN": "shpat_xxxxxxxxxxxxxxxxxxxxx"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Environment Variables
|
|
70
|
+
|
|
71
|
+
| Variable | Required | Default | Description |
|
|
72
|
+
|----------|----------|---------|-------------|
|
|
73
|
+
| `SHOPIFY_STORE_URL` | Yes | - | Store's myshopify.com domain |
|
|
74
|
+
| `SHOPIFY_ACCESS_TOKEN` | Yes | - | Admin API access token |
|
|
75
|
+
| `SHOPIFY_STOREFRONT_ACCESS_TOKEN` | No | - | Storefront API token |
|
|
76
|
+
| `SHOPIFY_API_VERSION` | No | `2025-01` | API version |
|
|
77
|
+
| `SHOPIFY_TIER` | No | `STANDARD` | Rate limit tier |
|
|
78
|
+
|
|
79
|
+
## Available Tools
|
|
80
|
+
|
|
81
|
+
### Core Tools
|
|
82
|
+
|
|
83
|
+
| Tool | Description |
|
|
84
|
+
|------|-------------|
|
|
85
|
+
| `get_shop_info` | Retrieve store configuration and settings |
|
|
86
|
+
| `run_graphql_query` | Execute any GraphQL query or mutation |
|
|
87
|
+
|
|
88
|
+
### Smart Tools
|
|
89
|
+
|
|
90
|
+
| Tool | Description |
|
|
91
|
+
|------|-------------|
|
|
92
|
+
| `upload_file` | Upload file from URL, poll until ready, return CDN URL |
|
|
93
|
+
| `bulk_export` | Start bulk query, poll completion, return JSONL download URL |
|
|
94
|
+
| `bulk_import` | Staged upload + bulk mutation with automatic polling |
|
|
95
|
+
| `upsert_metaobject` | Create or update metaobject by handle (idempotent) |
|
|
96
|
+
| `schema_discover` | Discover metafield definitions and metaobject types |
|
|
97
|
+
|
|
98
|
+
### Infrastructure Tools
|
|
99
|
+
|
|
100
|
+
| Tool | Description |
|
|
101
|
+
|------|-------------|
|
|
102
|
+
| `configure` | Set rate limit tier (manual or auto-detect from shop plan) |
|
|
103
|
+
| `get_history` | Query past operations for debugging |
|
|
104
|
+
| `get_stats` | Aggregated usage statistics |
|
|
105
|
+
|
|
106
|
+
## Rate Limiting
|
|
107
|
+
|
|
108
|
+
The server respects Shopify's rate limits based on your shop plan:
|
|
109
|
+
|
|
110
|
+
| Tier | Requests/sec | Plans |
|
|
111
|
+
|------|--------------|-------|
|
|
112
|
+
| STANDARD | 1 | Basic, Development, Lite |
|
|
113
|
+
| ADVANCED | 2 | Advanced |
|
|
114
|
+
| PLUS | 5 | Shopify Plus |
|
|
115
|
+
| ENTERPRISE | 10 | Commerce Components |
|
|
116
|
+
|
|
117
|
+
Use the `configure` tool to set your tier manually or auto-detect from your shop plan.
|
|
118
|
+
|
|
119
|
+
## Available Prompts
|
|
120
|
+
|
|
121
|
+
| Prompt | Description |
|
|
122
|
+
|--------|-------------|
|
|
123
|
+
| `analyze-product` | Product analysis template |
|
|
124
|
+
| `summarize-orders` | Order summary by timeframe |
|
|
125
|
+
| `inventory-health` | Inventory health check |
|
|
126
|
+
| `customer-insights` | Customer segment analysis |
|
|
127
|
+
| `custom-query` | Help building custom GraphQL queries |
|
|
128
|
+
|
|
129
|
+
## Available Resources
|
|
130
|
+
|
|
131
|
+
| Resource | Description |
|
|
132
|
+
|----------|-------------|
|
|
133
|
+
| `shopify://config` | Current store connection info |
|
|
134
|
+
| `shopify://docs/query-syntax` | Query syntax reference |
|
|
135
|
+
| `shopify://docs/gid-format` | GID format reference |
|
|
136
|
+
| `shopify://docs/scopes` | API scopes reference |
|
|
137
|
+
|
|
138
|
+
## Development
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
# Install dependencies
|
|
142
|
+
npm install
|
|
143
|
+
|
|
144
|
+
# Build
|
|
145
|
+
npm run build
|
|
146
|
+
|
|
147
|
+
# Run with inspector
|
|
148
|
+
npm run inspect
|
|
149
|
+
|
|
150
|
+
# Watch mode
|
|
151
|
+
npm run dev
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Database
|
|
155
|
+
|
|
156
|
+
The server uses SQLite for operation logging and configuration. The database is automatically created at `~/.shopify-mcp/mcp.db`.
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
# View database
|
|
160
|
+
npm run db:studio
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Security
|
|
164
|
+
|
|
165
|
+
- Never commit your `.env` file or access tokens
|
|
166
|
+
- Use environment variables or MCP config for credentials
|
|
167
|
+
- Access tokens should have minimal required scopes
|
|
168
|
+
- The server logs operations locally for debugging (disable with `MCP_LOG_OPERATIONS=false`)
|
|
169
|
+
|
|
170
|
+
## License
|
|
171
|
+
|
|
172
|
+
ISC
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface ShopifyConfig {
|
|
2
|
+
storeDomain: string;
|
|
3
|
+
adminAccessToken: string;
|
|
4
|
+
storefrontAccessToken?: string;
|
|
5
|
+
customerAccessToken?: string;
|
|
6
|
+
apiVersion: string;
|
|
7
|
+
bugReportEnabled: boolean;
|
|
8
|
+
bugReportDir?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function loadConfig(): ShopifyConfig;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { config as loadDotenv } from "dotenv";
|
|
2
|
+
import { mkdirSync, existsSync } from "fs";
|
|
3
|
+
import { join, dirname } from "path";
|
|
4
|
+
import { createHash } from "crypto";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
// Get the directory of this file for relative .env loading
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = dirname(__filename);
|
|
9
|
+
export function loadConfig() {
|
|
10
|
+
// Try to load .env files from the package root (one level up from dist/)
|
|
11
|
+
// Priority: .env.local > .env (local overrides shared)
|
|
12
|
+
const packageRoot = join(__dirname, "..");
|
|
13
|
+
loadDotenv({ path: join(packageRoot, ".env.local") });
|
|
14
|
+
loadDotenv({ path: join(packageRoot, ".env") });
|
|
15
|
+
const storeDomain = process.env.SHOPIFY_STORE_URL;
|
|
16
|
+
const adminAccessToken = process.env.SHOPIFY_ACCESS_TOKEN;
|
|
17
|
+
const storefrontAccessToken = process.env.SHOPIFY_STOREFRONT_ACCESS_TOKEN;
|
|
18
|
+
const customerAccessToken = process.env.SHOPIFY_CUSTOMER_ACCESS_TOKEN;
|
|
19
|
+
const apiVersion = process.env.SHOPIFY_API_VERSION || "2025-01";
|
|
20
|
+
const bugReportEnabled = process.env.SHOPIFY_MCP_BUG_REPORTS === "true";
|
|
21
|
+
const bugReportBaseDir = process.env.SHOPIFY_MCP_BUG_REPORT_DIR;
|
|
22
|
+
if (!storeDomain) {
|
|
23
|
+
throw new Error("SHOPIFY_STORE_URL is required. Set it to your store's myshopify.com domain (e.g. my-store.myshopify.com).");
|
|
24
|
+
}
|
|
25
|
+
if (!adminAccessToken) {
|
|
26
|
+
throw new Error("SHOPIFY_ACCESS_TOKEN is required. Create a custom app in Shopify Admin > Settings > Apps and development channels to get one.");
|
|
27
|
+
}
|
|
28
|
+
// Normalize: strip protocol and trailing slash
|
|
29
|
+
const normalized = storeDomain
|
|
30
|
+
.replace(/^https?:\/\//, "")
|
|
31
|
+
.replace(/\/$/, "");
|
|
32
|
+
// Create unique bug report directory per store
|
|
33
|
+
let bugReportDir;
|
|
34
|
+
if (bugReportEnabled) {
|
|
35
|
+
// Create a unique identifier from store domain + session timestamp
|
|
36
|
+
const sessionId = Date.now().toString(36);
|
|
37
|
+
const storeHash = createHash("md5")
|
|
38
|
+
.update(normalized)
|
|
39
|
+
.digest("hex")
|
|
40
|
+
.slice(0, 8);
|
|
41
|
+
const dirName = `${normalized.replace(/\./g, "-")}_${storeHash}_${sessionId}`;
|
|
42
|
+
bugReportDir = bugReportBaseDir
|
|
43
|
+
? join(bugReportBaseDir, dirName)
|
|
44
|
+
: join(process.cwd(), ".bug-reports", dirName);
|
|
45
|
+
// Create the directory if it doesn't exist
|
|
46
|
+
if (!existsSync(bugReportDir)) {
|
|
47
|
+
mkdirSync(bugReportDir, { recursive: true });
|
|
48
|
+
console.error(`[shopify-store-mcp] Bug reports enabled: ${bugReportDir}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// Debug logging for troubleshooting
|
|
52
|
+
console.error(`[shopify-store-mcp] Store domain: ${normalized}`);
|
|
53
|
+
console.error(`[shopify-store-mcp] Token prefix: ${adminAccessToken.substring(0, 8)}...`);
|
|
54
|
+
console.error(`[shopify-store-mcp] Token length: ${adminAccessToken.length} chars`);
|
|
55
|
+
return {
|
|
56
|
+
storeDomain: normalized,
|
|
57
|
+
adminAccessToken,
|
|
58
|
+
storefrontAccessToken,
|
|
59
|
+
customerAccessToken,
|
|
60
|
+
apiVersion,
|
|
61
|
+
bugReportEnabled,
|
|
62
|
+
bugReportDir,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=config.js.map
|
package/dist/db.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prisma client singleton for SQLite database
|
|
3
|
+
* Handles operation logging, store configuration, and background jobs
|
|
4
|
+
*/
|
|
5
|
+
import { PrismaClient } from "@prisma/client";
|
|
6
|
+
declare global {
|
|
7
|
+
var prismaGlobal: PrismaClient | undefined;
|
|
8
|
+
}
|
|
9
|
+
declare const prisma: PrismaClient<import("@prisma/client").Prisma.PrismaClientOptions, never, import("@prisma/client/runtime/library").DefaultArgs>;
|
|
10
|
+
export default prisma;
|
|
11
|
+
/**
|
|
12
|
+
* Initialize the database (create tables if they don't exist)
|
|
13
|
+
* This is called on MCP server startup
|
|
14
|
+
*/
|
|
15
|
+
export declare function initializeDatabase(): Promise<void>;
|
|
16
|
+
/**
|
|
17
|
+
* Clean up old operation logs based on retention period
|
|
18
|
+
*/
|
|
19
|
+
export declare function cleanupOldLogs(retentionDays: number): Promise<number>;
|
package/dist/db.js
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prisma client singleton for SQLite database
|
|
3
|
+
* Handles operation logging, store configuration, and background jobs
|
|
4
|
+
*/
|
|
5
|
+
import { PrismaClient } from "@prisma/client";
|
|
6
|
+
// Create Prisma client with optimized settings
|
|
7
|
+
const createPrismaClient = () => new PrismaClient({
|
|
8
|
+
log: process.env.NODE_ENV === "development"
|
|
9
|
+
? ["warn", "error"]
|
|
10
|
+
: ["error"],
|
|
11
|
+
});
|
|
12
|
+
// Use global singleton in development to prevent multiple instances during hot reload
|
|
13
|
+
if (process.env.NODE_ENV !== "production") {
|
|
14
|
+
if (!global.prismaGlobal) {
|
|
15
|
+
global.prismaGlobal = createPrismaClient();
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const prisma = global.prismaGlobal ?? createPrismaClient();
|
|
19
|
+
// Graceful shutdown handling
|
|
20
|
+
const gracefulShutdown = () => {
|
|
21
|
+
prisma
|
|
22
|
+
.$disconnect()
|
|
23
|
+
.then(() => {
|
|
24
|
+
process.exit(0);
|
|
25
|
+
})
|
|
26
|
+
.catch(() => {
|
|
27
|
+
process.exit(1);
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
process.on("SIGINT", gracefulShutdown);
|
|
31
|
+
process.on("SIGTERM", gracefulShutdown);
|
|
32
|
+
export default prisma;
|
|
33
|
+
/**
|
|
34
|
+
* Initialize the database (create tables if they don't exist)
|
|
35
|
+
* This is called on MCP server startup
|
|
36
|
+
*/
|
|
37
|
+
export async function initializeDatabase() {
|
|
38
|
+
try {
|
|
39
|
+
await prisma.$connect();
|
|
40
|
+
// Test if tables exist by trying a simple query
|
|
41
|
+
// If tables don't exist, this will fail and we'll create them
|
|
42
|
+
try {
|
|
43
|
+
await prisma.storeConfig.count();
|
|
44
|
+
console.error("[shopify-store-mcp] Database connected");
|
|
45
|
+
}
|
|
46
|
+
catch (tableError) {
|
|
47
|
+
// Tables don't exist - this is expected on first run
|
|
48
|
+
// Prisma will create them on first write
|
|
49
|
+
console.error("[shopify-store-mcp] Database initializing (first run)...");
|
|
50
|
+
// Create a dummy record to trigger table creation, then delete it
|
|
51
|
+
// This is a workaround since Prisma SQLite doesn't auto-create tables
|
|
52
|
+
try {
|
|
53
|
+
await prisma.$executeRaw `
|
|
54
|
+
CREATE TABLE IF NOT EXISTS "StoreConfig" (
|
|
55
|
+
"id" TEXT NOT NULL PRIMARY KEY,
|
|
56
|
+
"storeDomain" TEXT NOT NULL,
|
|
57
|
+
"tier" TEXT NOT NULL DEFAULT 'STANDARD',
|
|
58
|
+
"autoDetected" BOOLEAN NOT NULL DEFAULT false,
|
|
59
|
+
"shopName" TEXT,
|
|
60
|
+
"shopPlan" TEXT,
|
|
61
|
+
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
62
|
+
"updatedAt" DATETIME NOT NULL
|
|
63
|
+
)
|
|
64
|
+
`;
|
|
65
|
+
await prisma.$executeRaw `
|
|
66
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "StoreConfig_storeDomain_key" ON "StoreConfig"("storeDomain")
|
|
67
|
+
`;
|
|
68
|
+
await prisma.$executeRaw `
|
|
69
|
+
CREATE TABLE IF NOT EXISTS "OperationLog" (
|
|
70
|
+
"id" TEXT NOT NULL PRIMARY KEY,
|
|
71
|
+
"storeDomain" TEXT NOT NULL,
|
|
72
|
+
"sessionId" TEXT NOT NULL,
|
|
73
|
+
"toolName" TEXT NOT NULL,
|
|
74
|
+
"operationType" TEXT NOT NULL,
|
|
75
|
+
"query" TEXT NOT NULL,
|
|
76
|
+
"variables" TEXT,
|
|
77
|
+
"response" TEXT,
|
|
78
|
+
"success" BOOLEAN NOT NULL,
|
|
79
|
+
"errorMessage" TEXT,
|
|
80
|
+
"durationMs" INTEGER NOT NULL,
|
|
81
|
+
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
82
|
+
)
|
|
83
|
+
`;
|
|
84
|
+
await prisma.$executeRaw `
|
|
85
|
+
CREATE INDEX IF NOT EXISTS "OperationLog_storeDomain_idx" ON "OperationLog"("storeDomain")
|
|
86
|
+
`;
|
|
87
|
+
await prisma.$executeRaw `
|
|
88
|
+
CREATE INDEX IF NOT EXISTS "OperationLog_storeDomain_createdAt_idx" ON "OperationLog"("storeDomain", "createdAt")
|
|
89
|
+
`;
|
|
90
|
+
await prisma.$executeRaw `
|
|
91
|
+
CREATE INDEX IF NOT EXISTS "OperationLog_toolName_idx" ON "OperationLog"("toolName")
|
|
92
|
+
`;
|
|
93
|
+
await prisma.$executeRaw `
|
|
94
|
+
CREATE INDEX IF NOT EXISTS "OperationLog_sessionId_idx" ON "OperationLog"("sessionId")
|
|
95
|
+
`;
|
|
96
|
+
await prisma.$executeRaw `
|
|
97
|
+
CREATE INDEX IF NOT EXISTS "OperationLog_createdAt_idx" ON "OperationLog"("createdAt")
|
|
98
|
+
`;
|
|
99
|
+
await prisma.$executeRaw `
|
|
100
|
+
CREATE TABLE IF NOT EXISTS "BackgroundJob" (
|
|
101
|
+
"id" TEXT NOT NULL PRIMARY KEY,
|
|
102
|
+
"storeDomain" TEXT NOT NULL,
|
|
103
|
+
"sessionId" TEXT NOT NULL,
|
|
104
|
+
"bulkOperationId" TEXT,
|
|
105
|
+
"type" TEXT NOT NULL,
|
|
106
|
+
"name" TEXT NOT NULL,
|
|
107
|
+
"status" TEXT NOT NULL DEFAULT 'PENDING',
|
|
108
|
+
"progress" REAL NOT NULL DEFAULT 0,
|
|
109
|
+
"total" INTEGER NOT NULL DEFAULT 0,
|
|
110
|
+
"processed" INTEGER NOT NULL DEFAULT 0,
|
|
111
|
+
"data" TEXT,
|
|
112
|
+
"result" TEXT,
|
|
113
|
+
"error" TEXT,
|
|
114
|
+
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
115
|
+
"updatedAt" DATETIME NOT NULL
|
|
116
|
+
)
|
|
117
|
+
`;
|
|
118
|
+
await prisma.$executeRaw `
|
|
119
|
+
CREATE INDEX IF NOT EXISTS "BackgroundJob_storeDomain_idx" ON "BackgroundJob"("storeDomain")
|
|
120
|
+
`;
|
|
121
|
+
await prisma.$executeRaw `
|
|
122
|
+
CREATE INDEX IF NOT EXISTS "BackgroundJob_storeDomain_status_idx" ON "BackgroundJob"("storeDomain", "status")
|
|
123
|
+
`;
|
|
124
|
+
await prisma.$executeRaw `
|
|
125
|
+
CREATE INDEX IF NOT EXISTS "BackgroundJob_bulkOperationId_idx" ON "BackgroundJob"("bulkOperationId")
|
|
126
|
+
`;
|
|
127
|
+
await prisma.$executeRaw `
|
|
128
|
+
CREATE INDEX IF NOT EXISTS "BackgroundJob_status_idx" ON "BackgroundJob"("status")
|
|
129
|
+
`;
|
|
130
|
+
await prisma.$executeRaw `
|
|
131
|
+
CREATE INDEX IF NOT EXISTS "BackgroundJob_sessionId_idx" ON "BackgroundJob"("sessionId")
|
|
132
|
+
`;
|
|
133
|
+
console.error("[shopify-store-mcp] Database tables created");
|
|
134
|
+
}
|
|
135
|
+
catch (createError) {
|
|
136
|
+
console.error("[shopify-store-mcp] Failed to create tables:", createError);
|
|
137
|
+
throw createError;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
console.error("[shopify-store-mcp] Database connection failed:", error);
|
|
143
|
+
throw error;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Clean up old operation logs based on retention period
|
|
148
|
+
*/
|
|
149
|
+
export async function cleanupOldLogs(retentionDays) {
|
|
150
|
+
const cutoffDate = new Date();
|
|
151
|
+
cutoffDate.setDate(cutoffDate.getDate() - retentionDays);
|
|
152
|
+
const result = await prisma.operationLog.deleteMany({
|
|
153
|
+
where: {
|
|
154
|
+
createdAt: {
|
|
155
|
+
lt: cutoffDate,
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
return result.count;
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=db.js.map
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { UserError } from "./types.js";
|
|
2
|
+
export declare function formatErrorResponse(error: unknown): {
|
|
3
|
+
content: Array<{
|
|
4
|
+
type: "text";
|
|
5
|
+
text: string;
|
|
6
|
+
}>;
|
|
7
|
+
isError: true;
|
|
8
|
+
};
|
|
9
|
+
export declare function formatGraphQLErrors(response: {
|
|
10
|
+
errors?: unknown;
|
|
11
|
+
extensions?: Record<string, unknown>;
|
|
12
|
+
}): {
|
|
13
|
+
content: Array<{
|
|
14
|
+
type: "text";
|
|
15
|
+
text: string;
|
|
16
|
+
}>;
|
|
17
|
+
isError: true;
|
|
18
|
+
};
|
|
19
|
+
export declare function formatUserErrors(userErrors: UserError[]): {
|
|
20
|
+
content: Array<{
|
|
21
|
+
type: "text";
|
|
22
|
+
text: string;
|
|
23
|
+
}>;
|
|
24
|
+
isError: true;
|
|
25
|
+
};
|
|
26
|
+
export declare function formatSuccessResponse(data: unknown): {
|
|
27
|
+
content: Array<{
|
|
28
|
+
type: "text";
|
|
29
|
+
text: string;
|
|
30
|
+
}>;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Normalize a Shopify ID — accepts either a numeric ID or a full GID.
|
|
34
|
+
* e.g. "123" becomes "gid://shopify/Product/123" when resourceType is "Product"
|
|
35
|
+
*/
|
|
36
|
+
export declare function normalizeGid(id: string, resourceType: string): string;
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
export function formatErrorResponse(error) {
|
|
2
|
+
let message;
|
|
3
|
+
if (error instanceof Error) {
|
|
4
|
+
message = error.message;
|
|
5
|
+
}
|
|
6
|
+
else if (typeof error === "string") {
|
|
7
|
+
message = error;
|
|
8
|
+
}
|
|
9
|
+
else {
|
|
10
|
+
message = JSON.stringify(error);
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
14
|
+
isError: true,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export function formatGraphQLErrors(response) {
|
|
18
|
+
const parts = [];
|
|
19
|
+
if (response.errors) {
|
|
20
|
+
if (Array.isArray(response.errors)) {
|
|
21
|
+
for (const err of response.errors) {
|
|
22
|
+
parts.push(err.message || JSON.stringify(err));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
else if (typeof response.errors === "object") {
|
|
26
|
+
const errObj = response.errors;
|
|
27
|
+
if (errObj.networkStatusCode) {
|
|
28
|
+
const code = errObj.networkStatusCode;
|
|
29
|
+
parts.push(`HTTP ${code}`);
|
|
30
|
+
if (code === 401) {
|
|
31
|
+
parts.push("The access token is invalid or expired. Check SHOPIFY_ACCESS_TOKEN.");
|
|
32
|
+
}
|
|
33
|
+
else if (code === 403) {
|
|
34
|
+
parts.push("The access token lacks required scopes. Update your custom app's API access scopes in Shopify Admin.");
|
|
35
|
+
}
|
|
36
|
+
else if (code === 429) {
|
|
37
|
+
parts.push("Rate limited by Shopify. Wait a moment and retry.");
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (errObj.message) {
|
|
41
|
+
parts.push(String(errObj.message));
|
|
42
|
+
}
|
|
43
|
+
if (Array.isArray(errObj.graphQLErrors)) {
|
|
44
|
+
for (const gqlErr of errObj.graphQLErrors) {
|
|
45
|
+
parts.push(`GraphQL: ${gqlErr.message}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
parts.push(String(response.errors));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (parts.length === 0) {
|
|
54
|
+
parts.push("Unknown GraphQL error");
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
content: [{ type: "text", text: parts.join("\n") }],
|
|
58
|
+
isError: true,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export function formatUserErrors(userErrors) {
|
|
62
|
+
const lines = userErrors.map((e) => `- ${e.field.join(".")}: ${e.message}`);
|
|
63
|
+
return {
|
|
64
|
+
content: [
|
|
65
|
+
{
|
|
66
|
+
type: "text",
|
|
67
|
+
text: `Shopify validation errors:\n${lines.join("\n")}`,
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
isError: true,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
export function formatSuccessResponse(data) {
|
|
74
|
+
return {
|
|
75
|
+
content: [
|
|
76
|
+
{
|
|
77
|
+
type: "text",
|
|
78
|
+
text: JSON.stringify(data, null, 2),
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Normalize a Shopify ID — accepts either a numeric ID or a full GID.
|
|
85
|
+
* e.g. "123" becomes "gid://shopify/Product/123" when resourceType is "Product"
|
|
86
|
+
*/
|
|
87
|
+
export function normalizeGid(id, resourceType) {
|
|
88
|
+
if (id.startsWith("gid://")) {
|
|
89
|
+
return id;
|
|
90
|
+
}
|
|
91
|
+
return `gid://shopify/${resourceType}/${id}`;
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GraphQL queries for collection operations
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Get a paginated list of collections
|
|
6
|
+
* Returns both smart and custom collections with rules and product counts
|
|
7
|
+
*/
|
|
8
|
+
export declare const GET_COLLECTIONS = "#graphql\n query GetCollections($first: Int!, $after: String, $query: String) {\n collections(first: $first, after: $after, query: $query) {\n edges {\n node {\n id\n title\n handle\n descriptionHtml\n sortOrder\n productsCount {\n count\n }\n ruleSet {\n appliedDisjunctively\n rules {\n column\n relation\n condition\n }\n }\n image {\n url\n altText\n }\n updatedAt\n }\n cursor\n }\n pageInfo {\n hasNextPage\n endCursor\n }\n }\n }\n";
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GraphQL queries for collection operations
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Get a paginated list of collections
|
|
6
|
+
* Returns both smart and custom collections with rules and product counts
|
|
7
|
+
*/
|
|
8
|
+
export const GET_COLLECTIONS = `#graphql
|
|
9
|
+
query GetCollections($first: Int!, $after: String, $query: String) {
|
|
10
|
+
collections(first: $first, after: $after, query: $query) {
|
|
11
|
+
edges {
|
|
12
|
+
node {
|
|
13
|
+
id
|
|
14
|
+
title
|
|
15
|
+
handle
|
|
16
|
+
descriptionHtml
|
|
17
|
+
sortOrder
|
|
18
|
+
productsCount {
|
|
19
|
+
count
|
|
20
|
+
}
|
|
21
|
+
ruleSet {
|
|
22
|
+
appliedDisjunctively
|
|
23
|
+
rules {
|
|
24
|
+
column
|
|
25
|
+
relation
|
|
26
|
+
condition
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
image {
|
|
30
|
+
url
|
|
31
|
+
altText
|
|
32
|
+
}
|
|
33
|
+
updatedAt
|
|
34
|
+
}
|
|
35
|
+
cursor
|
|
36
|
+
}
|
|
37
|
+
pageInfo {
|
|
38
|
+
hasNextPage
|
|
39
|
+
endCursor
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
`;
|
|
44
|
+
//# sourceMappingURL=collections.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GraphQL queries for customer operations
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Get a paginated list of customers with basic info
|
|
6
|
+
* Supports search query filtering
|
|
7
|
+
*/
|
|
8
|
+
export declare const GET_CUSTOMERS = "#graphql\n query GetCustomers($first: Int!, $after: String, $query: String) {\n customers(first: $first, after: $after, query: $query) {\n edges {\n node {\n id\n displayName\n email\n phone\n state\n numberOfOrders\n amountSpent {\n amount\n currencyCode\n }\n createdAt\n updatedAt\n tags\n defaultAddress {\n city\n province\n country\n }\n }\n cursor\n }\n pageInfo {\n hasNextPage\n endCursor\n }\n }\n }\n";
|
|
9
|
+
/**
|
|
10
|
+
* Get a single customer by ID with full details
|
|
11
|
+
* Includes addresses, recent orders, and metafields
|
|
12
|
+
*/
|
|
13
|
+
export declare const GET_CUSTOMER = "#graphql\n query GetCustomer($id: ID!) {\n customer(id: $id) {\n id\n displayName\n firstName\n lastName\n email\n phone\n state\n note\n tags\n numberOfOrders\n amountSpent {\n amount\n currencyCode\n }\n createdAt\n updatedAt\n defaultAddress {\n address1\n address2\n city\n province\n country\n zip\n phone\n }\n addresses {\n address1\n address2\n city\n province\n country\n zip\n }\n orders(first: 10) {\n edges {\n node {\n id\n name\n createdAt\n displayFinancialStatus\n displayFulfillmentStatus\n totalPriceSet {\n shopMoney {\n amount\n currencyCode\n }\n }\n }\n }\n }\n metafields(first: 10) {\n edges {\n node {\n id\n namespace\n key\n value\n type\n }\n }\n }\n }\n }\n";
|