@klaudworks/shopify-mcp 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +531 -0
- package/dist/index.js +107 -0
- package/dist/lib/formatters.js +72 -0
- package/dist/lib/shopifyAuth.js +96 -0
- package/dist/lib/toolUtils.js +36 -0
- package/dist/tools/completeDraftOrder.js +76 -0
- package/dist/tools/createCustomer.js +142 -0
- package/dist/tools/createDraftOrder.js +160 -0
- package/dist/tools/createFulfillment.js +100 -0
- package/dist/tools/createProduct.js +123 -0
- package/dist/tools/createRefund.js +115 -0
- package/dist/tools/deleteCustomer.js +47 -0
- package/dist/tools/deleteMetafields.js +54 -0
- package/dist/tools/deleteProduct.js +43 -0
- package/dist/tools/deleteProductVariants.js +82 -0
- package/dist/tools/getCollectionById.js +123 -0
- package/dist/tools/getCollections.js +88 -0
- package/dist/tools/getCustomerById.js +122 -0
- package/dist/tools/getCustomerOrders.js +131 -0
- package/dist/tools/getCustomers.js +125 -0
- package/dist/tools/getFulfillmentOrders.js +106 -0
- package/dist/tools/getInventoryItems.js +86 -0
- package/dist/tools/getInventoryLevels.js +82 -0
- package/dist/tools/getLocations.js +85 -0
- package/dist/tools/getMarkets.js +91 -0
- package/dist/tools/getMetafieldDefinitions.js +112 -0
- package/dist/tools/getMetafields.js +68 -0
- package/dist/tools/getOrderById.js +212 -0
- package/dist/tools/getOrderRefundDetails.js +131 -0
- package/dist/tools/getOrderTransactions.js +85 -0
- package/dist/tools/getOrders.js +148 -0
- package/dist/tools/getPriceLists.js +92 -0
- package/dist/tools/getProductById.js +171 -0
- package/dist/tools/getProductVariantsDetailed.js +139 -0
- package/dist/tools/getProducts.js +155 -0
- package/dist/tools/getShopInfo.js +74 -0
- package/dist/tools/manageCustomerAddress.js +149 -0
- package/dist/tools/manageProductOptions.js +293 -0
- package/dist/tools/manageProductVariants.js +203 -0
- package/dist/tools/manageTags.js +79 -0
- package/dist/tools/mergeCustomers.js +74 -0
- package/dist/tools/orderCancel.js +77 -0
- package/dist/tools/orderCloseOpen.js +74 -0
- package/dist/tools/orderMarkAsPaid.js +51 -0
- package/dist/tools/registry.js +106 -0
- package/dist/tools/setInventoryQuantities.js +74 -0
- package/dist/tools/setMetafields.js +61 -0
- package/dist/tools/updateCustomer.js +119 -0
- package/dist/tools/updateOrder.js +131 -0
- package/dist/tools/updateProduct.js +132 -0
- package/package.json +66 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { gql } from "graphql-request";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { handleToolError } from "../lib/toolUtils.js";
|
|
4
|
+
const GetOrderTransactionsInputSchema = z.object({
|
|
5
|
+
orderId: z
|
|
6
|
+
.string()
|
|
7
|
+
.min(1)
|
|
8
|
+
.describe("The order ID (e.g. gid://shopify/Order/123 or just 123)"),
|
|
9
|
+
});
|
|
10
|
+
let shopifyClient;
|
|
11
|
+
const getOrderTransactions = {
|
|
12
|
+
name: "get-order-transactions",
|
|
13
|
+
description: "Get all payment transactions for an order including authorizations, captures, refunds, and voids with gateway, status, and amounts",
|
|
14
|
+
schema: GetOrderTransactionsInputSchema,
|
|
15
|
+
initialize(client) {
|
|
16
|
+
shopifyClient = client;
|
|
17
|
+
},
|
|
18
|
+
execute: async (input) => {
|
|
19
|
+
try {
|
|
20
|
+
const orderId = input.orderId.startsWith("gid://")
|
|
21
|
+
? input.orderId
|
|
22
|
+
: `gid://shopify/Order/${input.orderId}`;
|
|
23
|
+
const query = gql `
|
|
24
|
+
#graphql
|
|
25
|
+
|
|
26
|
+
query GetOrderTransactions($id: ID!) {
|
|
27
|
+
order(id: $id) {
|
|
28
|
+
id
|
|
29
|
+
name
|
|
30
|
+
transactions {
|
|
31
|
+
id
|
|
32
|
+
kind
|
|
33
|
+
status
|
|
34
|
+
gateway
|
|
35
|
+
formattedGateway
|
|
36
|
+
amountSet {
|
|
37
|
+
shopMoney {
|
|
38
|
+
amount
|
|
39
|
+
currencyCode
|
|
40
|
+
}
|
|
41
|
+
presentmentMoney {
|
|
42
|
+
amount
|
|
43
|
+
currencyCode
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
processedAt
|
|
47
|
+
createdAt
|
|
48
|
+
authorizationCode
|
|
49
|
+
authorizationExpiresAt
|
|
50
|
+
errorCode
|
|
51
|
+
test
|
|
52
|
+
parentTransaction {
|
|
53
|
+
id
|
|
54
|
+
kind
|
|
55
|
+
}
|
|
56
|
+
paymentDetails {
|
|
57
|
+
... on CardPaymentDetails {
|
|
58
|
+
company
|
|
59
|
+
number
|
|
60
|
+
name
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
receiptJson
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
`;
|
|
68
|
+
const data = await shopifyClient.request(query, { id: orderId });
|
|
69
|
+
if (!data.order) {
|
|
70
|
+
throw new Error(`Order not found: ${orderId}`);
|
|
71
|
+
}
|
|
72
|
+
const transactions = data.order.transactions;
|
|
73
|
+
return {
|
|
74
|
+
orderId: data.order.id,
|
|
75
|
+
orderName: data.order.name,
|
|
76
|
+
transactionsCount: transactions.length,
|
|
77
|
+
transactions,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
handleToolError("fetch order transactions", error);
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
export { getOrderTransactions };
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { gql } from "graphql-request";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { handleToolError, edgesToNodes } from "../lib/toolUtils.js";
|
|
4
|
+
import { formatOrderSummary } from "../lib/formatters.js";
|
|
5
|
+
// Input schema for getOrders
|
|
6
|
+
const GetOrdersInputSchema = z.object({
|
|
7
|
+
status: z.enum(["any", "open", "closed", "cancelled"]).default("any"),
|
|
8
|
+
limit: z.number().default(10),
|
|
9
|
+
after: z.string().optional().describe("Cursor for forward pagination"),
|
|
10
|
+
before: z.string().optional().describe("Cursor for backward pagination"),
|
|
11
|
+
sortKey: z.enum([
|
|
12
|
+
"CREATED_AT", "ORDER_NUMBER", "TOTAL_PRICE", "FINANCIAL_STATUS",
|
|
13
|
+
"FULFILLMENT_STATUS", "UPDATED_AT", "CUSTOMER_NAME", "PROCESSED_AT",
|
|
14
|
+
"ID", "RELEVANCE"
|
|
15
|
+
]).optional().describe("Sort key for orders"),
|
|
16
|
+
reverse: z.boolean().optional().describe("Reverse the sort order"),
|
|
17
|
+
query: z.string().optional().describe("Raw query string for advanced filtering (e.g. 'financial_status:paid fulfillment_status:shipped')")
|
|
18
|
+
});
|
|
19
|
+
// Will be initialized in index.ts
|
|
20
|
+
let shopifyClient;
|
|
21
|
+
const getOrders = {
|
|
22
|
+
name: "get-orders",
|
|
23
|
+
description: "Get orders with optional filtering by status",
|
|
24
|
+
schema: GetOrdersInputSchema,
|
|
25
|
+
// Add initialize method to set up the GraphQL client
|
|
26
|
+
initialize(client) {
|
|
27
|
+
shopifyClient = client;
|
|
28
|
+
},
|
|
29
|
+
execute: async (input) => {
|
|
30
|
+
try {
|
|
31
|
+
const { status, limit, after, before, sortKey, reverse, query: rawQuery } = input;
|
|
32
|
+
// Build query filters
|
|
33
|
+
const queryParts = [];
|
|
34
|
+
if (status !== "any") {
|
|
35
|
+
queryParts.push(`status:${status}`);
|
|
36
|
+
}
|
|
37
|
+
if (rawQuery) {
|
|
38
|
+
queryParts.push(rawQuery);
|
|
39
|
+
}
|
|
40
|
+
const queryFilter = queryParts.join(" ") || undefined;
|
|
41
|
+
const query = gql `
|
|
42
|
+
#graphql
|
|
43
|
+
|
|
44
|
+
query GetOrders($first: Int!, $query: String, $after: String, $before: String, $sortKey: OrderSortKeys, $reverse: Boolean) {
|
|
45
|
+
orders(first: $first, query: $query, after: $after, before: $before, sortKey: $sortKey, reverse: $reverse) {
|
|
46
|
+
edges {
|
|
47
|
+
node {
|
|
48
|
+
id
|
|
49
|
+
name
|
|
50
|
+
createdAt
|
|
51
|
+
displayFinancialStatus
|
|
52
|
+
displayFulfillmentStatus
|
|
53
|
+
totalPriceSet {
|
|
54
|
+
shopMoney {
|
|
55
|
+
amount
|
|
56
|
+
currencyCode
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
subtotalPriceSet {
|
|
60
|
+
shopMoney {
|
|
61
|
+
amount
|
|
62
|
+
currencyCode
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
totalShippingPriceSet {
|
|
66
|
+
shopMoney {
|
|
67
|
+
amount
|
|
68
|
+
currencyCode
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
totalTaxSet {
|
|
72
|
+
shopMoney {
|
|
73
|
+
amount
|
|
74
|
+
currencyCode
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
customer {
|
|
78
|
+
id
|
|
79
|
+
firstName
|
|
80
|
+
lastName
|
|
81
|
+
defaultEmailAddress {
|
|
82
|
+
emailAddress
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
shippingAddress {
|
|
86
|
+
address1
|
|
87
|
+
address2
|
|
88
|
+
city
|
|
89
|
+
provinceCode
|
|
90
|
+
zip
|
|
91
|
+
country
|
|
92
|
+
phone
|
|
93
|
+
}
|
|
94
|
+
lineItems(first: 10) {
|
|
95
|
+
edges {
|
|
96
|
+
node {
|
|
97
|
+
id
|
|
98
|
+
title
|
|
99
|
+
quantity
|
|
100
|
+
originalTotalSet {
|
|
101
|
+
shopMoney {
|
|
102
|
+
amount
|
|
103
|
+
currencyCode
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
variant {
|
|
107
|
+
id
|
|
108
|
+
title
|
|
109
|
+
sku
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
tags
|
|
115
|
+
note
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
pageInfo {
|
|
119
|
+
hasNextPage
|
|
120
|
+
hasPreviousPage
|
|
121
|
+
startCursor
|
|
122
|
+
endCursor
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
`;
|
|
127
|
+
const variables = {
|
|
128
|
+
first: limit,
|
|
129
|
+
query: queryFilter,
|
|
130
|
+
...(after && { after }),
|
|
131
|
+
...(before && { before }),
|
|
132
|
+
...(sortKey && { sortKey }),
|
|
133
|
+
...(reverse !== undefined && { reverse })
|
|
134
|
+
};
|
|
135
|
+
const data = (await shopifyClient.request(query, variables));
|
|
136
|
+
// Extract and format order data
|
|
137
|
+
const orders = edgesToNodes(data.orders).map(formatOrderSummary);
|
|
138
|
+
return {
|
|
139
|
+
orders,
|
|
140
|
+
pageInfo: data.orders.pageInfo
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
handleToolError("fetch orders", error);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
export { getOrders };
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { gql } from "graphql-request";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { edgesToNodes, handleToolError } from "../lib/toolUtils.js";
|
|
4
|
+
const GetPriceListsInputSchema = z.object({
|
|
5
|
+
first: z
|
|
6
|
+
.number()
|
|
7
|
+
.min(1)
|
|
8
|
+
.max(50)
|
|
9
|
+
.default(25)
|
|
10
|
+
.optional()
|
|
11
|
+
.describe("Number of price lists to return (default 25, max 50)"),
|
|
12
|
+
});
|
|
13
|
+
let shopifyClient;
|
|
14
|
+
const getPriceLists = {
|
|
15
|
+
name: "get-price-lists",
|
|
16
|
+
description: "Get all price lists with their currency, fixed/relative adjustments, and associated catalog context",
|
|
17
|
+
schema: GetPriceListsInputSchema,
|
|
18
|
+
initialize(client) {
|
|
19
|
+
shopifyClient = client;
|
|
20
|
+
},
|
|
21
|
+
execute: async (input) => {
|
|
22
|
+
try {
|
|
23
|
+
const query = gql `
|
|
24
|
+
#graphql
|
|
25
|
+
|
|
26
|
+
query GetPriceLists($first: Int!) {
|
|
27
|
+
priceLists(first: $first) {
|
|
28
|
+
edges {
|
|
29
|
+
node {
|
|
30
|
+
id
|
|
31
|
+
name
|
|
32
|
+
currency
|
|
33
|
+
fixedPricesCount
|
|
34
|
+
parent {
|
|
35
|
+
adjustment {
|
|
36
|
+
type
|
|
37
|
+
value
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catalog {
|
|
41
|
+
... on MarketCatalog {
|
|
42
|
+
id
|
|
43
|
+
title
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
prices(first: 10) {
|
|
47
|
+
edges {
|
|
48
|
+
node {
|
|
49
|
+
variant {
|
|
50
|
+
id
|
|
51
|
+
title
|
|
52
|
+
product {
|
|
53
|
+
id
|
|
54
|
+
title
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
price {
|
|
58
|
+
amount
|
|
59
|
+
currencyCode
|
|
60
|
+
}
|
|
61
|
+
compareAtPrice {
|
|
62
|
+
amount
|
|
63
|
+
currencyCode
|
|
64
|
+
}
|
|
65
|
+
originType
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
`;
|
|
74
|
+
const variables = {
|
|
75
|
+
first: input.first ?? 25,
|
|
76
|
+
};
|
|
77
|
+
const data = await shopifyClient.request(query, variables);
|
|
78
|
+
const priceLists = edgesToNodes(data.priceLists).map((priceList) => ({
|
|
79
|
+
...priceList,
|
|
80
|
+
prices: edgesToNodes(priceList.prices),
|
|
81
|
+
}));
|
|
82
|
+
return {
|
|
83
|
+
priceListsCount: priceLists.length,
|
|
84
|
+
priceLists,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
handleToolError("fetch price lists", error);
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
export { getPriceLists };
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { gql } from "graphql-request";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { handleToolError } from "../lib/toolUtils.js";
|
|
4
|
+
// Input schema for getProductById
|
|
5
|
+
const GetProductByIdInputSchema = z.object({
|
|
6
|
+
productId: z.string().min(1)
|
|
7
|
+
});
|
|
8
|
+
// Will be initialized in index.ts
|
|
9
|
+
let shopifyClient;
|
|
10
|
+
const getProductById = {
|
|
11
|
+
name: "get-product-by-id",
|
|
12
|
+
description: "Get a specific product by ID",
|
|
13
|
+
schema: GetProductByIdInputSchema,
|
|
14
|
+
// Add initialize method to set up the GraphQL client
|
|
15
|
+
initialize(client) {
|
|
16
|
+
shopifyClient = client;
|
|
17
|
+
},
|
|
18
|
+
execute: async (input) => {
|
|
19
|
+
try {
|
|
20
|
+
const { productId } = input;
|
|
21
|
+
const query = gql `
|
|
22
|
+
#graphql
|
|
23
|
+
|
|
24
|
+
query GetProductById($id: ID!) {
|
|
25
|
+
product(id: $id) {
|
|
26
|
+
id
|
|
27
|
+
title
|
|
28
|
+
description
|
|
29
|
+
handle
|
|
30
|
+
status
|
|
31
|
+
createdAt
|
|
32
|
+
updatedAt
|
|
33
|
+
totalInventory
|
|
34
|
+
priceRangeV2 {
|
|
35
|
+
minVariantPrice {
|
|
36
|
+
amount
|
|
37
|
+
currencyCode
|
|
38
|
+
}
|
|
39
|
+
maxVariantPrice {
|
|
40
|
+
amount
|
|
41
|
+
currencyCode
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
media(first: 5) {
|
|
45
|
+
edges {
|
|
46
|
+
node {
|
|
47
|
+
... on MediaImage {
|
|
48
|
+
id
|
|
49
|
+
image {
|
|
50
|
+
url
|
|
51
|
+
altText
|
|
52
|
+
width
|
|
53
|
+
height
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
variants(first: 20) {
|
|
60
|
+
edges {
|
|
61
|
+
node {
|
|
62
|
+
id
|
|
63
|
+
title
|
|
64
|
+
price
|
|
65
|
+
inventoryQuantity
|
|
66
|
+
sku
|
|
67
|
+
selectedOptions {
|
|
68
|
+
name
|
|
69
|
+
value
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
collections(first: 5) {
|
|
75
|
+
edges {
|
|
76
|
+
node {
|
|
77
|
+
id
|
|
78
|
+
title
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
tags
|
|
83
|
+
vendor
|
|
84
|
+
productType
|
|
85
|
+
descriptionHtml
|
|
86
|
+
seo {
|
|
87
|
+
title
|
|
88
|
+
description
|
|
89
|
+
}
|
|
90
|
+
options {
|
|
91
|
+
id
|
|
92
|
+
name
|
|
93
|
+
position
|
|
94
|
+
optionValues {
|
|
95
|
+
id
|
|
96
|
+
name
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
`;
|
|
102
|
+
const variables = {
|
|
103
|
+
id: productId
|
|
104
|
+
};
|
|
105
|
+
const data = (await shopifyClient.request(query, variables));
|
|
106
|
+
if (!data.product) {
|
|
107
|
+
throw new Error(`Product with ID ${productId} not found`);
|
|
108
|
+
}
|
|
109
|
+
// Format product data
|
|
110
|
+
const product = data.product;
|
|
111
|
+
// Format variants
|
|
112
|
+
const variants = product.variants.edges.map((variantEdge) => ({
|
|
113
|
+
id: variantEdge.node.id,
|
|
114
|
+
title: variantEdge.node.title,
|
|
115
|
+
price: variantEdge.node.price,
|
|
116
|
+
inventoryQuantity: variantEdge.node.inventoryQuantity,
|
|
117
|
+
sku: variantEdge.node.sku,
|
|
118
|
+
options: variantEdge.node.selectedOptions
|
|
119
|
+
}));
|
|
120
|
+
// Format images from media
|
|
121
|
+
const images = product.media.edges
|
|
122
|
+
.filter((mediaEdge) => mediaEdge.node.image)
|
|
123
|
+
.map((mediaEdge) => ({
|
|
124
|
+
id: mediaEdge.node.id,
|
|
125
|
+
url: mediaEdge.node.image.url,
|
|
126
|
+
altText: mediaEdge.node.image.altText,
|
|
127
|
+
width: mediaEdge.node.image.width,
|
|
128
|
+
height: mediaEdge.node.image.height
|
|
129
|
+
}));
|
|
130
|
+
// Format collections
|
|
131
|
+
const collections = product.collections.edges.map((collectionEdge) => ({
|
|
132
|
+
id: collectionEdge.node.id,
|
|
133
|
+
title: collectionEdge.node.title
|
|
134
|
+
}));
|
|
135
|
+
const formattedProduct = {
|
|
136
|
+
id: product.id,
|
|
137
|
+
title: product.title,
|
|
138
|
+
description: product.description,
|
|
139
|
+
handle: product.handle,
|
|
140
|
+
status: product.status,
|
|
141
|
+
createdAt: product.createdAt,
|
|
142
|
+
updatedAt: product.updatedAt,
|
|
143
|
+
totalInventory: product.totalInventory,
|
|
144
|
+
priceRange: {
|
|
145
|
+
minPrice: {
|
|
146
|
+
amount: product.priceRangeV2.minVariantPrice.amount,
|
|
147
|
+
currencyCode: product.priceRangeV2.minVariantPrice.currencyCode
|
|
148
|
+
},
|
|
149
|
+
maxPrice: {
|
|
150
|
+
amount: product.priceRangeV2.maxVariantPrice.amount,
|
|
151
|
+
currencyCode: product.priceRangeV2.maxVariantPrice.currencyCode
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
images,
|
|
155
|
+
variants,
|
|
156
|
+
collections,
|
|
157
|
+
tags: product.tags,
|
|
158
|
+
vendor: product.vendor,
|
|
159
|
+
productType: product.productType,
|
|
160
|
+
descriptionHtml: product.descriptionHtml,
|
|
161
|
+
seo: product.seo,
|
|
162
|
+
options: product.options
|
|
163
|
+
};
|
|
164
|
+
return { product: formattedProduct };
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
handleToolError("fetch product", error);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
export { getProductById };
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { gql } from "graphql-request";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { edgesToNodes, handleToolError } from "../lib/toolUtils.js";
|
|
4
|
+
const GetProductVariantsDetailedInputSchema = z.object({
|
|
5
|
+
productId: z
|
|
6
|
+
.string()
|
|
7
|
+
.min(1)
|
|
8
|
+
.describe("The product ID (e.g. gid://shopify/Product/123 or just 123)"),
|
|
9
|
+
first: z
|
|
10
|
+
.number()
|
|
11
|
+
.min(1)
|
|
12
|
+
.max(100)
|
|
13
|
+
.default(50)
|
|
14
|
+
.optional()
|
|
15
|
+
.describe("Number of variants to return (default 50, max 100)"),
|
|
16
|
+
});
|
|
17
|
+
let shopifyClient;
|
|
18
|
+
const getProductVariantsDetailed = {
|
|
19
|
+
name: "get-product-variants-detailed",
|
|
20
|
+
description: "Get all variant fields for a product: pricing, inventory, barcode, weight, tax code, selected options, metafields, and image",
|
|
21
|
+
schema: GetProductVariantsDetailedInputSchema,
|
|
22
|
+
initialize(client) {
|
|
23
|
+
shopifyClient = client;
|
|
24
|
+
},
|
|
25
|
+
execute: async (input) => {
|
|
26
|
+
try {
|
|
27
|
+
const productId = input.productId.startsWith("gid://")
|
|
28
|
+
? input.productId
|
|
29
|
+
: `gid://shopify/Product/${input.productId}`;
|
|
30
|
+
const query = gql `
|
|
31
|
+
#graphql
|
|
32
|
+
|
|
33
|
+
query GetProductVariantsDetailed($id: ID!, $first: Int!) {
|
|
34
|
+
product(id: $id) {
|
|
35
|
+
id
|
|
36
|
+
title
|
|
37
|
+
variants(first: $first) {
|
|
38
|
+
edges {
|
|
39
|
+
node {
|
|
40
|
+
id
|
|
41
|
+
title
|
|
42
|
+
displayName
|
|
43
|
+
sku
|
|
44
|
+
barcode
|
|
45
|
+
price
|
|
46
|
+
compareAtPrice
|
|
47
|
+
taxable
|
|
48
|
+
availableForSale
|
|
49
|
+
inventoryQuantity
|
|
50
|
+
position
|
|
51
|
+
createdAt
|
|
52
|
+
updatedAt
|
|
53
|
+
selectedOptions {
|
|
54
|
+
name
|
|
55
|
+
value
|
|
56
|
+
}
|
|
57
|
+
media(first: 1) {
|
|
58
|
+
edges {
|
|
59
|
+
node {
|
|
60
|
+
... on MediaImage {
|
|
61
|
+
image {
|
|
62
|
+
url
|
|
63
|
+
altText
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
inventoryItem {
|
|
70
|
+
id
|
|
71
|
+
tracked
|
|
72
|
+
requiresShipping
|
|
73
|
+
unitCost {
|
|
74
|
+
amount
|
|
75
|
+
currencyCode
|
|
76
|
+
}
|
|
77
|
+
measurement {
|
|
78
|
+
weight {
|
|
79
|
+
unit
|
|
80
|
+
value
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
metafields(first: 25) {
|
|
85
|
+
edges {
|
|
86
|
+
node {
|
|
87
|
+
namespace
|
|
88
|
+
key
|
|
89
|
+
value
|
|
90
|
+
type
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
pageInfo {
|
|
97
|
+
hasNextPage
|
|
98
|
+
endCursor
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
`;
|
|
104
|
+
const data = await shopifyClient.request(query, {
|
|
105
|
+
id: productId,
|
|
106
|
+
first: input.first ?? 50,
|
|
107
|
+
});
|
|
108
|
+
if (!data.product) {
|
|
109
|
+
throw new Error(`Product not found: ${productId}`);
|
|
110
|
+
}
|
|
111
|
+
const variants = edgesToNodes(data.product.variants).map((variant) => {
|
|
112
|
+
const mediaNodes = variant.media
|
|
113
|
+
? edgesToNodes(variant.media)
|
|
114
|
+
: [];
|
|
115
|
+
const firstImage = mediaNodes.find((m) => m.image);
|
|
116
|
+
const image = firstImage?.image ?? null;
|
|
117
|
+
delete variant.media;
|
|
118
|
+
return {
|
|
119
|
+
...variant,
|
|
120
|
+
image,
|
|
121
|
+
metafields: variant.metafields
|
|
122
|
+
? edgesToNodes(variant.metafields)
|
|
123
|
+
: [],
|
|
124
|
+
};
|
|
125
|
+
});
|
|
126
|
+
return {
|
|
127
|
+
productId: data.product.id,
|
|
128
|
+
productTitle: data.product.title,
|
|
129
|
+
variantsCount: variants.length,
|
|
130
|
+
variants,
|
|
131
|
+
pageInfo: data.product.variants.pageInfo,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
handleToolError("fetch product variants", error);
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
export { getProductVariantsDetailed };
|