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/dist/logger.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Operation logger for tracking all GraphQL calls
|
|
3
|
+
* Logs to SQLite database for debugging and history
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Get the current session ID
|
|
7
|
+
*/
|
|
8
|
+
export declare function getSessionId(): string;
|
|
9
|
+
/**
|
|
10
|
+
* Log a GraphQL operation
|
|
11
|
+
*/
|
|
12
|
+
export declare function logOperation(params: {
|
|
13
|
+
storeDomain: string;
|
|
14
|
+
toolName: string;
|
|
15
|
+
query: string;
|
|
16
|
+
variables?: Record<string, unknown>;
|
|
17
|
+
response?: unknown;
|
|
18
|
+
success: boolean;
|
|
19
|
+
errorMessage?: string;
|
|
20
|
+
durationMs: number;
|
|
21
|
+
}): Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Query operation history
|
|
24
|
+
*/
|
|
25
|
+
export declare function getOperationHistory(params: {
|
|
26
|
+
storeDomain: string;
|
|
27
|
+
toolName?: string;
|
|
28
|
+
operationType?: "query" | "mutation";
|
|
29
|
+
success?: boolean;
|
|
30
|
+
since?: Date;
|
|
31
|
+
limit?: number;
|
|
32
|
+
}): Promise<Array<{
|
|
33
|
+
id: string;
|
|
34
|
+
toolName: string;
|
|
35
|
+
operationType: string;
|
|
36
|
+
query: string;
|
|
37
|
+
success: boolean;
|
|
38
|
+
errorMessage: string | null;
|
|
39
|
+
durationMs: number;
|
|
40
|
+
createdAt: Date;
|
|
41
|
+
}>>;
|
|
42
|
+
/**
|
|
43
|
+
* Get aggregated statistics
|
|
44
|
+
*/
|
|
45
|
+
export declare function getOperationStats(params: {
|
|
46
|
+
storeDomain: string;
|
|
47
|
+
since: Date;
|
|
48
|
+
}): Promise<{
|
|
49
|
+
totalCalls: number;
|
|
50
|
+
successCount: number;
|
|
51
|
+
errorCount: number;
|
|
52
|
+
avgDurationMs: number;
|
|
53
|
+
byTool: Record<string, {
|
|
54
|
+
calls: number;
|
|
55
|
+
successRate: number;
|
|
56
|
+
avgDuration: number;
|
|
57
|
+
}>;
|
|
58
|
+
}>;
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Operation logger for tracking all GraphQL calls
|
|
3
|
+
* Logs to SQLite database for debugging and history
|
|
4
|
+
*/
|
|
5
|
+
import prisma from "./db.js";
|
|
6
|
+
import { randomUUID } from "crypto";
|
|
7
|
+
// Session ID for this MCP process instance
|
|
8
|
+
const sessionId = randomUUID();
|
|
9
|
+
// Check if logging is enabled (default: true)
|
|
10
|
+
const isLoggingEnabled = process.env.MCP_LOG_OPERATIONS !== "false";
|
|
11
|
+
// Max length for query/response strings in DB
|
|
12
|
+
const MAX_QUERY_LENGTH = 10000;
|
|
13
|
+
const MAX_RESPONSE_LENGTH = 50000;
|
|
14
|
+
/**
|
|
15
|
+
* Get the current session ID
|
|
16
|
+
*/
|
|
17
|
+
export function getSessionId() {
|
|
18
|
+
return sessionId;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Truncate a string to a maximum length
|
|
22
|
+
*/
|
|
23
|
+
function truncate(str, maxLength) {
|
|
24
|
+
if (str.length <= maxLength) {
|
|
25
|
+
return str;
|
|
26
|
+
}
|
|
27
|
+
return str.slice(0, maxLength) + "... [truncated]";
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Detect if a GraphQL operation is a query or mutation
|
|
31
|
+
*/
|
|
32
|
+
function detectOperationType(query) {
|
|
33
|
+
const trimmed = query.trim().toLowerCase();
|
|
34
|
+
if (trimmed.startsWith("mutation")) {
|
|
35
|
+
return "mutation";
|
|
36
|
+
}
|
|
37
|
+
return "query";
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Log a GraphQL operation
|
|
41
|
+
*/
|
|
42
|
+
export async function logOperation(params) {
|
|
43
|
+
if (!isLoggingEnabled) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
await prisma.operationLog.create({
|
|
48
|
+
data: {
|
|
49
|
+
storeDomain: params.storeDomain,
|
|
50
|
+
sessionId,
|
|
51
|
+
toolName: params.toolName,
|
|
52
|
+
operationType: detectOperationType(params.query),
|
|
53
|
+
query: truncate(params.query, MAX_QUERY_LENGTH),
|
|
54
|
+
variables: params.variables
|
|
55
|
+
? truncate(JSON.stringify(params.variables), MAX_QUERY_LENGTH)
|
|
56
|
+
: null,
|
|
57
|
+
response: params.response
|
|
58
|
+
? truncate(JSON.stringify(params.response), MAX_RESPONSE_LENGTH)
|
|
59
|
+
: null,
|
|
60
|
+
success: params.success,
|
|
61
|
+
errorMessage: params.errorMessage || null,
|
|
62
|
+
durationMs: params.durationMs,
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
// Don't fail the operation if logging fails
|
|
68
|
+
console.error("[shopify-store-mcp] Failed to log operation:", error);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Query operation history
|
|
73
|
+
*/
|
|
74
|
+
export async function getOperationHistory(params) {
|
|
75
|
+
const where = {
|
|
76
|
+
storeDomain: params.storeDomain,
|
|
77
|
+
};
|
|
78
|
+
if (params.toolName) {
|
|
79
|
+
where.toolName = params.toolName;
|
|
80
|
+
}
|
|
81
|
+
if (params.operationType) {
|
|
82
|
+
where.operationType = params.operationType;
|
|
83
|
+
}
|
|
84
|
+
if (params.success !== undefined) {
|
|
85
|
+
where.success = params.success;
|
|
86
|
+
}
|
|
87
|
+
if (params.since) {
|
|
88
|
+
where.createdAt = { gte: params.since };
|
|
89
|
+
}
|
|
90
|
+
return prisma.operationLog.findMany({
|
|
91
|
+
where,
|
|
92
|
+
orderBy: { createdAt: "desc" },
|
|
93
|
+
take: params.limit || 50,
|
|
94
|
+
select: {
|
|
95
|
+
id: true,
|
|
96
|
+
toolName: true,
|
|
97
|
+
operationType: true,
|
|
98
|
+
query: true,
|
|
99
|
+
success: true,
|
|
100
|
+
errorMessage: true,
|
|
101
|
+
durationMs: true,
|
|
102
|
+
createdAt: true,
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Get aggregated statistics
|
|
108
|
+
*/
|
|
109
|
+
export async function getOperationStats(params) {
|
|
110
|
+
// Get all operations in the period
|
|
111
|
+
const operations = await prisma.operationLog.findMany({
|
|
112
|
+
where: {
|
|
113
|
+
storeDomain: params.storeDomain,
|
|
114
|
+
createdAt: { gte: params.since },
|
|
115
|
+
},
|
|
116
|
+
select: {
|
|
117
|
+
toolName: true,
|
|
118
|
+
success: true,
|
|
119
|
+
durationMs: true,
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
if (operations.length === 0) {
|
|
123
|
+
return {
|
|
124
|
+
totalCalls: 0,
|
|
125
|
+
successCount: 0,
|
|
126
|
+
errorCount: 0,
|
|
127
|
+
avgDurationMs: 0,
|
|
128
|
+
byTool: {},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
const totalCalls = operations.length;
|
|
132
|
+
const successCount = operations.filter((op) => op.success).length;
|
|
133
|
+
const errorCount = totalCalls - successCount;
|
|
134
|
+
const avgDurationMs = operations.reduce((sum, op) => sum + op.durationMs, 0) / totalCalls;
|
|
135
|
+
// Group by tool
|
|
136
|
+
const byTool = {};
|
|
137
|
+
const toolGroups = new Map();
|
|
138
|
+
for (const op of operations) {
|
|
139
|
+
const group = toolGroups.get(op.toolName) || {
|
|
140
|
+
total: 0,
|
|
141
|
+
success: 0,
|
|
142
|
+
duration: 0,
|
|
143
|
+
};
|
|
144
|
+
group.total++;
|
|
145
|
+
if (op.success)
|
|
146
|
+
group.success++;
|
|
147
|
+
group.duration += op.durationMs;
|
|
148
|
+
toolGroups.set(op.toolName, group);
|
|
149
|
+
}
|
|
150
|
+
for (const [toolName, group] of toolGroups) {
|
|
151
|
+
byTool[toolName] = {
|
|
152
|
+
calls: group.total,
|
|
153
|
+
successRate: group.success / group.total,
|
|
154
|
+
avgDuration: group.duration / group.total,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
totalCalls,
|
|
159
|
+
successCount,
|
|
160
|
+
errorCount,
|
|
161
|
+
avgDurationMs,
|
|
162
|
+
byTool,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export function registerAllPrompts(server) {
|
|
3
|
+
// Product Analysis Prompt
|
|
4
|
+
server.registerPrompt("analyze-product", {
|
|
5
|
+
title: "Analyze Product",
|
|
6
|
+
description: "Generate a detailed analysis of a product including its variants, pricing, inventory status, and recommendations for optimization.",
|
|
7
|
+
argsSchema: {
|
|
8
|
+
productId: z
|
|
9
|
+
.string()
|
|
10
|
+
.describe("The product ID (GID or numeric) to analyze"),
|
|
11
|
+
},
|
|
12
|
+
}, async ({ productId }) => ({
|
|
13
|
+
messages: [
|
|
14
|
+
{
|
|
15
|
+
role: "user",
|
|
16
|
+
content: {
|
|
17
|
+
type: "text",
|
|
18
|
+
text: `Please analyze the product with ID "${productId}". Use the get_product tool to fetch its details, then provide:
|
|
19
|
+
|
|
20
|
+
1. **Product Overview**: Title, vendor, type, and current status
|
|
21
|
+
2. **Pricing Analysis**: Current prices across variants, compare-at prices if set
|
|
22
|
+
3. **Inventory Status**: Stock levels, which variants are low or out of stock
|
|
23
|
+
4. **SEO Review**: Handle, title length, description quality
|
|
24
|
+
5. **Recommendations**: Actionable suggestions to improve the product listing
|
|
25
|
+
|
|
26
|
+
Start by fetching the product data.`,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
}));
|
|
31
|
+
// Order Summary Prompt
|
|
32
|
+
server.registerPrompt("summarize-orders", {
|
|
33
|
+
title: "Summarize Recent Orders",
|
|
34
|
+
description: "Generate a summary of recent orders including revenue, fulfillment status, and trends.",
|
|
35
|
+
argsSchema: {
|
|
36
|
+
timeframe: z
|
|
37
|
+
.enum(["today", "week", "month"])
|
|
38
|
+
.default("week")
|
|
39
|
+
.describe("The timeframe to analyze"),
|
|
40
|
+
limit: z
|
|
41
|
+
.number()
|
|
42
|
+
.default(50)
|
|
43
|
+
.describe("Number of orders to fetch"),
|
|
44
|
+
},
|
|
45
|
+
}, async ({ timeframe, limit }) => {
|
|
46
|
+
const dateFilter = timeframe === "today"
|
|
47
|
+
? "created_at:>=today"
|
|
48
|
+
: timeframe === "week"
|
|
49
|
+
? "created_at:>=7_days_ago"
|
|
50
|
+
: "created_at:>=30_days_ago";
|
|
51
|
+
return {
|
|
52
|
+
messages: [
|
|
53
|
+
{
|
|
54
|
+
role: "user",
|
|
55
|
+
content: {
|
|
56
|
+
type: "text",
|
|
57
|
+
text: `Please provide a summary of orders from the ${timeframe}. Use the get_orders tool with query "${dateFilter}" and first=${limit}.
|
|
58
|
+
|
|
59
|
+
Include in your analysis:
|
|
60
|
+
1. **Total Orders**: Count and total revenue
|
|
61
|
+
2. **Fulfillment Status**: Breakdown by unfulfilled, partially fulfilled, fulfilled
|
|
62
|
+
3. **Payment Status**: Breakdown by paid, pending, refunded
|
|
63
|
+
4. **Top Products**: Most ordered items
|
|
64
|
+
5. **Average Order Value**: Calculate and compare to typical benchmarks
|
|
65
|
+
|
|
66
|
+
Start by fetching the order data.`,
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
// Inventory Health Check Prompt
|
|
73
|
+
server.registerPrompt("inventory-health", {
|
|
74
|
+
title: "Inventory Health Check",
|
|
75
|
+
description: "Analyze inventory levels across products and identify items that need attention (low stock, overstock, etc.).",
|
|
76
|
+
argsSchema: {
|
|
77
|
+
threshold: z
|
|
78
|
+
.number()
|
|
79
|
+
.default(10)
|
|
80
|
+
.describe("Low stock threshold to flag items"),
|
|
81
|
+
},
|
|
82
|
+
}, async ({ threshold }) => ({
|
|
83
|
+
messages: [
|
|
84
|
+
{
|
|
85
|
+
role: "user",
|
|
86
|
+
content: {
|
|
87
|
+
type: "text",
|
|
88
|
+
text: `Please perform an inventory health check. Use the get_inventory tool to fetch inventory levels.
|
|
89
|
+
|
|
90
|
+
Analyze and report:
|
|
91
|
+
1. **Low Stock Items**: Products with inventory below ${threshold} units
|
|
92
|
+
2. **Out of Stock**: Items with zero inventory
|
|
93
|
+
3. **Overstock Candidates**: Items with unusually high inventory (if detectable)
|
|
94
|
+
4. **Inventory Distribution**: Summary by location if multiple locations exist
|
|
95
|
+
5. **Action Items**: Prioritized list of items needing restocking
|
|
96
|
+
|
|
97
|
+
Start by fetching inventory data.`,
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
}));
|
|
102
|
+
// Customer Insights Prompt
|
|
103
|
+
server.registerPrompt("customer-insights", {
|
|
104
|
+
title: "Customer Insights",
|
|
105
|
+
description: "Analyze customer data to identify patterns, VIP customers, and engagement opportunities.",
|
|
106
|
+
argsSchema: {
|
|
107
|
+
segment: z
|
|
108
|
+
.enum(["all", "repeat", "high-value", "recent"])
|
|
109
|
+
.default("all")
|
|
110
|
+
.describe("Customer segment to analyze"),
|
|
111
|
+
},
|
|
112
|
+
}, async ({ segment }) => {
|
|
113
|
+
const queryMap = {
|
|
114
|
+
all: "",
|
|
115
|
+
repeat: "orders_count:>1",
|
|
116
|
+
"high-value": "total_spent:>500",
|
|
117
|
+
recent: "updated_at:>=30_days_ago",
|
|
118
|
+
};
|
|
119
|
+
return {
|
|
120
|
+
messages: [
|
|
121
|
+
{
|
|
122
|
+
role: "user",
|
|
123
|
+
content: {
|
|
124
|
+
type: "text",
|
|
125
|
+
text: `Please analyze the "${segment}" customer segment. Use the get_customers tool${queryMap[segment] ? ` with query "${queryMap[segment]}"` : ""}.
|
|
126
|
+
|
|
127
|
+
Provide insights on:
|
|
128
|
+
1. **Customer Count**: Total customers in this segment
|
|
129
|
+
2. **Engagement Metrics**: Average orders, total spent
|
|
130
|
+
3. **Geographic Distribution**: Where customers are located
|
|
131
|
+
4. **VIP Identification**: Top customers by order count or spend
|
|
132
|
+
5. **Opportunities**: Suggestions for engagement or retention
|
|
133
|
+
|
|
134
|
+
Start by fetching customer data.`,
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
};
|
|
139
|
+
});
|
|
140
|
+
// Custom GraphQL Query Prompt
|
|
141
|
+
server.registerPrompt("custom-query", {
|
|
142
|
+
title: "Custom GraphQL Query",
|
|
143
|
+
description: "Help construct and execute a custom GraphQL query against the Shopify Admin API.",
|
|
144
|
+
argsSchema: {
|
|
145
|
+
intent: z
|
|
146
|
+
.string()
|
|
147
|
+
.describe("What data do you want to fetch or what action do you want to perform?"),
|
|
148
|
+
},
|
|
149
|
+
}, async ({ intent }) => ({
|
|
150
|
+
messages: [
|
|
151
|
+
{
|
|
152
|
+
role: "user",
|
|
153
|
+
content: {
|
|
154
|
+
type: "text",
|
|
155
|
+
text: `I need help with a custom Shopify Admin API query. My goal is: "${intent}"
|
|
156
|
+
|
|
157
|
+
Please:
|
|
158
|
+
1. Determine if this requires a query (read) or mutation (write)
|
|
159
|
+
2. Construct the appropriate GraphQL operation
|
|
160
|
+
3. Use the run_graphql_query tool to execute it
|
|
161
|
+
4. Explain the results
|
|
162
|
+
|
|
163
|
+
If you're unsure about the exact schema, start with a simpler query and iterate based on what fields are available.`,
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
],
|
|
167
|
+
}));
|
|
168
|
+
}
|
|
169
|
+
//# sourceMappingURL=index.js.map
|
package/dist/queue.d.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate-limited queue for Shopify API calls
|
|
3
|
+
* Respects Shopify's rate limits based on shop plan tier
|
|
4
|
+
*/
|
|
5
|
+
export declare const SHOPIFY_TIER_CONFIGS: {
|
|
6
|
+
readonly STANDARD: {
|
|
7
|
+
readonly name: "Standard Shopify";
|
|
8
|
+
readonly concurrency: 1;
|
|
9
|
+
readonly interval: 1000;
|
|
10
|
+
readonly intervalCap: 1;
|
|
11
|
+
};
|
|
12
|
+
readonly ADVANCED: {
|
|
13
|
+
readonly name: "Advanced Shopify";
|
|
14
|
+
readonly concurrency: 1;
|
|
15
|
+
readonly interval: 1000;
|
|
16
|
+
readonly intervalCap: 2;
|
|
17
|
+
};
|
|
18
|
+
readonly PLUS: {
|
|
19
|
+
readonly name: "Shopify Plus";
|
|
20
|
+
readonly concurrency: 2;
|
|
21
|
+
readonly interval: 1000;
|
|
22
|
+
readonly intervalCap: 5;
|
|
23
|
+
};
|
|
24
|
+
readonly ENTERPRISE: {
|
|
25
|
+
readonly name: "Shopify for Enterprise (Commerce Components)";
|
|
26
|
+
readonly concurrency: 3;
|
|
27
|
+
readonly interval: 1000;
|
|
28
|
+
readonly intervalCap: 10;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
export type ShopifyTier = keyof typeof SHOPIFY_TIER_CONFIGS;
|
|
32
|
+
/**
|
|
33
|
+
* Get the current queue configuration
|
|
34
|
+
*/
|
|
35
|
+
export declare function getCurrentTierInfo(): {
|
|
36
|
+
tier: ShopifyTier;
|
|
37
|
+
config: (typeof SHOPIFY_TIER_CONFIGS)[ShopifyTier];
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Update the queue to use a different tier's rate limits
|
|
41
|
+
*/
|
|
42
|
+
export declare function updateQueue(tier: ShopifyTier): void;
|
|
43
|
+
/**
|
|
44
|
+
* Detect Shopify tier from shop plan information
|
|
45
|
+
*/
|
|
46
|
+
export declare function detectTierFromPlan(plan: {
|
|
47
|
+
shopifyPlus?: boolean;
|
|
48
|
+
displayName?: string;
|
|
49
|
+
}): ShopifyTier;
|
|
50
|
+
/**
|
|
51
|
+
* Enqueue a function to be executed with rate limiting
|
|
52
|
+
*/
|
|
53
|
+
export declare function enqueue<T>(fn: () => Promise<T>): Promise<T>;
|
|
54
|
+
/**
|
|
55
|
+
* Get queue statistics
|
|
56
|
+
*/
|
|
57
|
+
export declare function getQueueStats(): {
|
|
58
|
+
pending: number;
|
|
59
|
+
size: number;
|
|
60
|
+
isPaused: boolean;
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Pause the queue (useful for graceful shutdown)
|
|
64
|
+
*/
|
|
65
|
+
export declare function pauseQueue(): void;
|
|
66
|
+
/**
|
|
67
|
+
* Resume the queue
|
|
68
|
+
*/
|
|
69
|
+
export declare function resumeQueue(): void;
|
|
70
|
+
/**
|
|
71
|
+
* Clear all pending items from the queue
|
|
72
|
+
*/
|
|
73
|
+
export declare function clearQueue(): void;
|
package/dist/queue.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate-limited queue for Shopify API calls
|
|
3
|
+
* Respects Shopify's rate limits based on shop plan tier
|
|
4
|
+
*/
|
|
5
|
+
import PQueue from "p-queue";
|
|
6
|
+
// Shopify Admin API rate limit configurations
|
|
7
|
+
// https://shopify.dev/docs/api/usage/limits#rate-limits
|
|
8
|
+
export const SHOPIFY_TIER_CONFIGS = {
|
|
9
|
+
STANDARD: {
|
|
10
|
+
name: "Standard Shopify",
|
|
11
|
+
// GraphQL Admin API: 100 points/second, bucket size 1000 points
|
|
12
|
+
// Conservative settings: ~1 request/second
|
|
13
|
+
concurrency: 1,
|
|
14
|
+
interval: 1000,
|
|
15
|
+
intervalCap: 1,
|
|
16
|
+
},
|
|
17
|
+
ADVANCED: {
|
|
18
|
+
name: "Advanced Shopify",
|
|
19
|
+
// GraphQL Admin API: 200 points/second
|
|
20
|
+
concurrency: 1,
|
|
21
|
+
interval: 1000,
|
|
22
|
+
intervalCap: 2,
|
|
23
|
+
},
|
|
24
|
+
PLUS: {
|
|
25
|
+
name: "Shopify Plus",
|
|
26
|
+
// GraphQL Admin API: 1000 points/second
|
|
27
|
+
concurrency: 2,
|
|
28
|
+
interval: 1000,
|
|
29
|
+
intervalCap: 5,
|
|
30
|
+
},
|
|
31
|
+
ENTERPRISE: {
|
|
32
|
+
name: "Shopify for Enterprise (Commerce Components)",
|
|
33
|
+
// GraphQL Admin API: 2000 points/second
|
|
34
|
+
concurrency: 3,
|
|
35
|
+
interval: 1000,
|
|
36
|
+
intervalCap: 10,
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
// Global queue instance
|
|
40
|
+
let queue = new PQueue(SHOPIFY_TIER_CONFIGS.STANDARD);
|
|
41
|
+
let currentTier = "STANDARD";
|
|
42
|
+
/**
|
|
43
|
+
* Get the current queue configuration
|
|
44
|
+
*/
|
|
45
|
+
export function getCurrentTierInfo() {
|
|
46
|
+
return {
|
|
47
|
+
tier: currentTier,
|
|
48
|
+
config: SHOPIFY_TIER_CONFIGS[currentTier],
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Update the queue to use a different tier's rate limits
|
|
53
|
+
*/
|
|
54
|
+
export function updateQueue(tier) {
|
|
55
|
+
const config = SHOPIFY_TIER_CONFIGS[tier];
|
|
56
|
+
// Clear existing queue and create new one
|
|
57
|
+
queue.clear();
|
|
58
|
+
queue = new PQueue({
|
|
59
|
+
concurrency: config.concurrency,
|
|
60
|
+
interval: config.interval,
|
|
61
|
+
intervalCap: config.intervalCap,
|
|
62
|
+
});
|
|
63
|
+
currentTier = tier;
|
|
64
|
+
console.error(`[shopify-store-mcp] Queue updated to ${config.name} tier (${tier})`);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Detect Shopify tier from shop plan information
|
|
68
|
+
*/
|
|
69
|
+
export function detectTierFromPlan(plan) {
|
|
70
|
+
// Check for Shopify Plus first (most specific)
|
|
71
|
+
if (plan.shopifyPlus) {
|
|
72
|
+
return "PLUS";
|
|
73
|
+
}
|
|
74
|
+
// Check display name for other tiers
|
|
75
|
+
const displayName = (plan.displayName || "").toLowerCase();
|
|
76
|
+
if (displayName.includes("advanced")) {
|
|
77
|
+
return "ADVANCED";
|
|
78
|
+
}
|
|
79
|
+
if (displayName.includes("enterprise") ||
|
|
80
|
+
displayName.includes("commerce components")) {
|
|
81
|
+
return "ENTERPRISE";
|
|
82
|
+
}
|
|
83
|
+
// Default to STANDARD for Basic, Development, Grow, Lite, etc.
|
|
84
|
+
return "STANDARD";
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Enqueue a function to be executed with rate limiting
|
|
88
|
+
*/
|
|
89
|
+
export async function enqueue(fn) {
|
|
90
|
+
return queue.add(fn);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Get queue statistics
|
|
94
|
+
*/
|
|
95
|
+
export function getQueueStats() {
|
|
96
|
+
return {
|
|
97
|
+
pending: queue.pending,
|
|
98
|
+
size: queue.size,
|
|
99
|
+
isPaused: queue.isPaused,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Pause the queue (useful for graceful shutdown)
|
|
104
|
+
*/
|
|
105
|
+
export function pauseQueue() {
|
|
106
|
+
queue.pause();
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Resume the queue
|
|
110
|
+
*/
|
|
111
|
+
export function resumeQueue() {
|
|
112
|
+
queue.start();
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Clear all pending items from the queue
|
|
116
|
+
*/
|
|
117
|
+
export function clearQueue() {
|
|
118
|
+
queue.clear();
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=queue.js.map
|