@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,122 @@
|
|
|
1
|
+
import { gql } from "graphql-request";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { handleToolError, edgesToNodes } from "../lib/toolUtils.js";
|
|
4
|
+
// Input schema for getting a customer by ID
|
|
5
|
+
const GetCustomerByIdInputSchema = z.object({
|
|
6
|
+
id: z.string().regex(/^\d+$/, "Customer ID must be numeric")
|
|
7
|
+
});
|
|
8
|
+
// Will be initialized in index.ts
|
|
9
|
+
let shopifyClient;
|
|
10
|
+
const getCustomerById = {
|
|
11
|
+
name: "get-customer-by-id",
|
|
12
|
+
description: "Get a single customer by ID",
|
|
13
|
+
schema: GetCustomerByIdInputSchema,
|
|
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 { id } = input;
|
|
21
|
+
// Convert numeric ID to GID format
|
|
22
|
+
const customerGid = `gid://shopify/Customer/${id}`;
|
|
23
|
+
const query = gql `
|
|
24
|
+
#graphql
|
|
25
|
+
|
|
26
|
+
query GetCustomerById($id: ID!) {
|
|
27
|
+
customer(id: $id) {
|
|
28
|
+
id
|
|
29
|
+
firstName
|
|
30
|
+
lastName
|
|
31
|
+
defaultEmailAddress {
|
|
32
|
+
emailAddress
|
|
33
|
+
}
|
|
34
|
+
defaultPhoneNumber {
|
|
35
|
+
phoneNumber
|
|
36
|
+
}
|
|
37
|
+
createdAt
|
|
38
|
+
updatedAt
|
|
39
|
+
tags
|
|
40
|
+
note
|
|
41
|
+
taxExempt
|
|
42
|
+
defaultAddress {
|
|
43
|
+
address1
|
|
44
|
+
address2
|
|
45
|
+
city
|
|
46
|
+
provinceCode
|
|
47
|
+
zip
|
|
48
|
+
country
|
|
49
|
+
phone
|
|
50
|
+
}
|
|
51
|
+
addressesV2(first: 10) {
|
|
52
|
+
edges {
|
|
53
|
+
node {
|
|
54
|
+
address1
|
|
55
|
+
address2
|
|
56
|
+
city
|
|
57
|
+
provinceCode
|
|
58
|
+
zip
|
|
59
|
+
country
|
|
60
|
+
phone
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
amountSpent {
|
|
65
|
+
amount
|
|
66
|
+
currencyCode
|
|
67
|
+
}
|
|
68
|
+
numberOfOrders
|
|
69
|
+
metafields(first: 10) {
|
|
70
|
+
edges {
|
|
71
|
+
node {
|
|
72
|
+
id
|
|
73
|
+
namespace
|
|
74
|
+
key
|
|
75
|
+
value
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
`;
|
|
82
|
+
const variables = {
|
|
83
|
+
id: customerGid
|
|
84
|
+
};
|
|
85
|
+
const data = (await shopifyClient.request(query, variables));
|
|
86
|
+
if (!data.customer) {
|
|
87
|
+
throw new Error(`Customer with ID ${id} not found`);
|
|
88
|
+
}
|
|
89
|
+
const customer = data.customer;
|
|
90
|
+
// Format metafields if they exist
|
|
91
|
+
const metafields = customer.metafields
|
|
92
|
+
? edgesToNodes(customer.metafields)
|
|
93
|
+
: [];
|
|
94
|
+
const addresses = customer.addressesV2
|
|
95
|
+
? edgesToNodes(customer.addressesV2)
|
|
96
|
+
: [];
|
|
97
|
+
return {
|
|
98
|
+
customer: {
|
|
99
|
+
id: customer.id,
|
|
100
|
+
firstName: customer.firstName,
|
|
101
|
+
lastName: customer.lastName,
|
|
102
|
+
email: customer.defaultEmailAddress?.emailAddress || null,
|
|
103
|
+
phone: customer.defaultPhoneNumber?.phoneNumber || null,
|
|
104
|
+
createdAt: customer.createdAt,
|
|
105
|
+
updatedAt: customer.updatedAt,
|
|
106
|
+
tags: customer.tags,
|
|
107
|
+
note: customer.note,
|
|
108
|
+
taxExempt: customer.taxExempt,
|
|
109
|
+
defaultAddress: customer.defaultAddress,
|
|
110
|
+
addresses,
|
|
111
|
+
amountSpent: customer.amountSpent,
|
|
112
|
+
numberOfOrders: customer.numberOfOrders,
|
|
113
|
+
metafields
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
handleToolError("fetch customer", error);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
export { getCustomerById };
|
|
@@ -0,0 +1,131 @@
|
|
|
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 getting customer orders
|
|
6
|
+
const GetCustomerOrdersInputSchema = z.object({
|
|
7
|
+
customerId: z.string().regex(/^\d+$/, "Customer ID must be numeric"),
|
|
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
|
+
});
|
|
18
|
+
// Will be initialized in index.ts
|
|
19
|
+
let shopifyClient;
|
|
20
|
+
const getCustomerOrders = {
|
|
21
|
+
name: "get-customer-orders",
|
|
22
|
+
description: "Get orders for a specific customer",
|
|
23
|
+
schema: GetCustomerOrdersInputSchema,
|
|
24
|
+
// Add initialize method to set up the GraphQL client
|
|
25
|
+
initialize(client) {
|
|
26
|
+
shopifyClient = client;
|
|
27
|
+
},
|
|
28
|
+
execute: async (input) => {
|
|
29
|
+
try {
|
|
30
|
+
const { customerId, limit, after, before, sortKey, reverse } = input;
|
|
31
|
+
// Query to get orders for a specific customer
|
|
32
|
+
const query = gql `
|
|
33
|
+
#graphql
|
|
34
|
+
|
|
35
|
+
query GetCustomerOrders($query: String!, $first: Int!, $after: String, $before: String, $sortKey: OrderSortKeys, $reverse: Boolean) {
|
|
36
|
+
orders(query: $query, first: $first, after: $after, before: $before, sortKey: $sortKey, reverse: $reverse) {
|
|
37
|
+
edges {
|
|
38
|
+
node {
|
|
39
|
+
id
|
|
40
|
+
name
|
|
41
|
+
createdAt
|
|
42
|
+
displayFinancialStatus
|
|
43
|
+
displayFulfillmentStatus
|
|
44
|
+
totalPriceSet {
|
|
45
|
+
shopMoney {
|
|
46
|
+
amount
|
|
47
|
+
currencyCode
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
subtotalPriceSet {
|
|
51
|
+
shopMoney {
|
|
52
|
+
amount
|
|
53
|
+
currencyCode
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
totalShippingPriceSet {
|
|
57
|
+
shopMoney {
|
|
58
|
+
amount
|
|
59
|
+
currencyCode
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
totalTaxSet {
|
|
63
|
+
shopMoney {
|
|
64
|
+
amount
|
|
65
|
+
currencyCode
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
customer {
|
|
69
|
+
id
|
|
70
|
+
firstName
|
|
71
|
+
lastName
|
|
72
|
+
defaultEmailAddress {
|
|
73
|
+
emailAddress
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
lineItems(first: 5) {
|
|
77
|
+
edges {
|
|
78
|
+
node {
|
|
79
|
+
id
|
|
80
|
+
title
|
|
81
|
+
quantity
|
|
82
|
+
originalTotalSet {
|
|
83
|
+
shopMoney {
|
|
84
|
+
amount
|
|
85
|
+
currencyCode
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
variant {
|
|
89
|
+
id
|
|
90
|
+
title
|
|
91
|
+
sku
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
tags
|
|
97
|
+
note
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
pageInfo {
|
|
101
|
+
hasNextPage
|
|
102
|
+
hasPreviousPage
|
|
103
|
+
startCursor
|
|
104
|
+
endCursor
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
`;
|
|
109
|
+
// We use the query parameter to filter orders by customer ID
|
|
110
|
+
const variables = {
|
|
111
|
+
query: `customer_id:${customerId}`,
|
|
112
|
+
first: limit,
|
|
113
|
+
...(after && { after }),
|
|
114
|
+
...(before && { before }),
|
|
115
|
+
...(sortKey && { sortKey }),
|
|
116
|
+
...(reverse !== undefined && { reverse })
|
|
117
|
+
};
|
|
118
|
+
const data = (await shopifyClient.request(query, variables));
|
|
119
|
+
// Extract and format order data
|
|
120
|
+
const orders = edgesToNodes(data.orders).map(formatOrderSummary);
|
|
121
|
+
return {
|
|
122
|
+
orders,
|
|
123
|
+
pageInfo: data.orders.pageInfo
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
handleToolError("fetch customer orders", error);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
export { getCustomerOrders };
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { gql } from "graphql-request";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { handleToolError, edgesToNodes } from "../lib/toolUtils.js";
|
|
4
|
+
// Input schema for getCustomers
|
|
5
|
+
const GetCustomersInputSchema = z.object({
|
|
6
|
+
searchQuery: z.string().optional().describe("Freetext search or Shopify query syntax (e.g. 'country:US tag:vip orders_count:>5')"),
|
|
7
|
+
limit: z.number().default(10),
|
|
8
|
+
after: z.string().optional().describe("Cursor for forward pagination"),
|
|
9
|
+
before: z.string().optional().describe("Cursor for backward pagination"),
|
|
10
|
+
sortKey: z.enum([
|
|
11
|
+
"CREATED_AT", "ID", "LAST_UPDATE", "LOCATION", "NAME",
|
|
12
|
+
"ORDERS_COUNT", "RELEVANCE", "TOTAL_SPENT", "UPDATED_AT"
|
|
13
|
+
]).optional().describe("Sort key for customers"),
|
|
14
|
+
reverse: z.boolean().optional().describe("Reverse the sort order")
|
|
15
|
+
});
|
|
16
|
+
// Will be initialized in index.ts
|
|
17
|
+
let shopifyClient;
|
|
18
|
+
const getCustomers = {
|
|
19
|
+
name: "get-customers",
|
|
20
|
+
description: "Get customers or search by name/email",
|
|
21
|
+
schema: GetCustomersInputSchema,
|
|
22
|
+
// Add initialize method to set up the GraphQL client
|
|
23
|
+
initialize(client) {
|
|
24
|
+
shopifyClient = client;
|
|
25
|
+
},
|
|
26
|
+
execute: async (input) => {
|
|
27
|
+
try {
|
|
28
|
+
const { searchQuery, limit, after, before, sortKey, reverse } = input;
|
|
29
|
+
const query = gql `
|
|
30
|
+
#graphql
|
|
31
|
+
|
|
32
|
+
query GetCustomers($first: Int!, $query: String, $after: String, $before: String, $sortKey: CustomerSortKeys, $reverse: Boolean) {
|
|
33
|
+
customers(first: $first, query: $query, after: $after, before: $before, sortKey: $sortKey, reverse: $reverse) {
|
|
34
|
+
edges {
|
|
35
|
+
node {
|
|
36
|
+
id
|
|
37
|
+
firstName
|
|
38
|
+
lastName
|
|
39
|
+
defaultEmailAddress {
|
|
40
|
+
emailAddress
|
|
41
|
+
}
|
|
42
|
+
defaultPhoneNumber {
|
|
43
|
+
phoneNumber
|
|
44
|
+
}
|
|
45
|
+
createdAt
|
|
46
|
+
updatedAt
|
|
47
|
+
tags
|
|
48
|
+
defaultAddress {
|
|
49
|
+
address1
|
|
50
|
+
address2
|
|
51
|
+
city
|
|
52
|
+
provinceCode
|
|
53
|
+
zip
|
|
54
|
+
country
|
|
55
|
+
phone
|
|
56
|
+
}
|
|
57
|
+
addressesV2(first: 10) {
|
|
58
|
+
edges {
|
|
59
|
+
node {
|
|
60
|
+
address1
|
|
61
|
+
address2
|
|
62
|
+
city
|
|
63
|
+
provinceCode
|
|
64
|
+
zip
|
|
65
|
+
country
|
|
66
|
+
phone
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
amountSpent {
|
|
71
|
+
amount
|
|
72
|
+
currencyCode
|
|
73
|
+
}
|
|
74
|
+
numberOfOrders
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
pageInfo {
|
|
78
|
+
hasNextPage
|
|
79
|
+
hasPreviousPage
|
|
80
|
+
startCursor
|
|
81
|
+
endCursor
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
`;
|
|
86
|
+
const variables = {
|
|
87
|
+
first: limit,
|
|
88
|
+
query: searchQuery,
|
|
89
|
+
...(after && { after }),
|
|
90
|
+
...(before && { before }),
|
|
91
|
+
...(sortKey && { sortKey }),
|
|
92
|
+
...(reverse !== undefined && { reverse })
|
|
93
|
+
};
|
|
94
|
+
const data = (await shopifyClient.request(query, variables));
|
|
95
|
+
// Extract and format customer data
|
|
96
|
+
const customers = data.customers.edges.map((edge) => {
|
|
97
|
+
const customer = edge.node;
|
|
98
|
+
return {
|
|
99
|
+
id: customer.id,
|
|
100
|
+
firstName: customer.firstName,
|
|
101
|
+
lastName: customer.lastName,
|
|
102
|
+
email: customer.defaultEmailAddress?.emailAddress || null,
|
|
103
|
+
phone: customer.defaultPhoneNumber?.phoneNumber || null,
|
|
104
|
+
createdAt: customer.createdAt,
|
|
105
|
+
updatedAt: customer.updatedAt,
|
|
106
|
+
tags: customer.tags,
|
|
107
|
+
defaultAddress: customer.defaultAddress,
|
|
108
|
+
addresses: customer.addressesV2
|
|
109
|
+
? edgesToNodes(customer.addressesV2)
|
|
110
|
+
: [],
|
|
111
|
+
amountSpent: customer.amountSpent,
|
|
112
|
+
numberOfOrders: customer.numberOfOrders
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
return {
|
|
116
|
+
customers,
|
|
117
|
+
pageInfo: data.customers.pageInfo
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
handleToolError("fetch customers", error);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
export { getCustomers };
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { gql } from "graphql-request";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { edgesToNodes, handleToolError } from "../lib/toolUtils.js";
|
|
4
|
+
const GetFulfillmentOrdersInputSchema = 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 getFulfillmentOrders = {
|
|
12
|
+
name: "get-fulfillment-orders",
|
|
13
|
+
description: "Get fulfillment orders for an order including status, assigned location, delivery method, holds, and line items",
|
|
14
|
+
schema: GetFulfillmentOrdersInputSchema,
|
|
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 GetFulfillmentOrders($id: ID!) {
|
|
27
|
+
order(id: $id) {
|
|
28
|
+
id
|
|
29
|
+
name
|
|
30
|
+
fulfillmentOrders(first: 25) {
|
|
31
|
+
edges {
|
|
32
|
+
node {
|
|
33
|
+
id
|
|
34
|
+
status
|
|
35
|
+
requestStatus
|
|
36
|
+
createdAt
|
|
37
|
+
updatedAt
|
|
38
|
+
fulfillAt
|
|
39
|
+
fulfillBy
|
|
40
|
+
assignedLocation {
|
|
41
|
+
name
|
|
42
|
+
address1
|
|
43
|
+
address2
|
|
44
|
+
city
|
|
45
|
+
province
|
|
46
|
+
countryCode
|
|
47
|
+
zip
|
|
48
|
+
phone
|
|
49
|
+
}
|
|
50
|
+
deliveryMethod {
|
|
51
|
+
methodType
|
|
52
|
+
minDeliveryDateTime
|
|
53
|
+
maxDeliveryDateTime
|
|
54
|
+
presentedName
|
|
55
|
+
serviceCode
|
|
56
|
+
}
|
|
57
|
+
fulfillmentHolds {
|
|
58
|
+
id
|
|
59
|
+
reason
|
|
60
|
+
reasonNotes
|
|
61
|
+
displayReason
|
|
62
|
+
heldByRequestingApp
|
|
63
|
+
}
|
|
64
|
+
lineItems(first: 50) {
|
|
65
|
+
edges {
|
|
66
|
+
node {
|
|
67
|
+
id
|
|
68
|
+
totalQuantity
|
|
69
|
+
remainingQuantity
|
|
70
|
+
productTitle
|
|
71
|
+
variantTitle
|
|
72
|
+
sku
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
supportedActions {
|
|
77
|
+
action
|
|
78
|
+
externalUrl
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
`;
|
|
86
|
+
const data = await shopifyClient.request(query, { id: orderId });
|
|
87
|
+
if (!data.order) {
|
|
88
|
+
throw new Error(`Order not found: ${orderId}`);
|
|
89
|
+
}
|
|
90
|
+
const fulfillmentOrders = edgesToNodes(data.order.fulfillmentOrders).map((fo) => ({
|
|
91
|
+
...fo,
|
|
92
|
+
lineItems: edgesToNodes(fo.lineItems),
|
|
93
|
+
}));
|
|
94
|
+
return {
|
|
95
|
+
orderId: data.order.id,
|
|
96
|
+
orderName: data.order.name,
|
|
97
|
+
fulfillmentOrdersCount: fulfillmentOrders.length,
|
|
98
|
+
fulfillmentOrders,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
handleToolError("fetch fulfillment orders", error);
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
export { getFulfillmentOrders };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { gql } from "graphql-request";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { edgesToNodes, handleToolError } from "../lib/toolUtils.js";
|
|
4
|
+
const GetInventoryItemsInputSchema = 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
|
+
});
|
|
10
|
+
let shopifyClient;
|
|
11
|
+
const getInventoryItems = {
|
|
12
|
+
name: "get-inventory-items",
|
|
13
|
+
description: "Get inventory item details for all variants of a product including SKU, cost, tracked status, country of origin, and HS codes",
|
|
14
|
+
schema: GetInventoryItemsInputSchema,
|
|
15
|
+
initialize(client) {
|
|
16
|
+
shopifyClient = client;
|
|
17
|
+
},
|
|
18
|
+
execute: async (input) => {
|
|
19
|
+
try {
|
|
20
|
+
const productId = input.productId.startsWith("gid://")
|
|
21
|
+
? input.productId
|
|
22
|
+
: `gid://shopify/Product/${input.productId}`;
|
|
23
|
+
const query = gql `
|
|
24
|
+
#graphql
|
|
25
|
+
|
|
26
|
+
query GetInventoryItems($id: ID!) {
|
|
27
|
+
product(id: $id) {
|
|
28
|
+
id
|
|
29
|
+
title
|
|
30
|
+
variants(first: 100) {
|
|
31
|
+
edges {
|
|
32
|
+
node {
|
|
33
|
+
id
|
|
34
|
+
title
|
|
35
|
+
sku
|
|
36
|
+
inventoryItem {
|
|
37
|
+
id
|
|
38
|
+
sku
|
|
39
|
+
tracked
|
|
40
|
+
requiresShipping
|
|
41
|
+
unitCost {
|
|
42
|
+
amount
|
|
43
|
+
currencyCode
|
|
44
|
+
}
|
|
45
|
+
countryCodeOfOrigin
|
|
46
|
+
provinceCodeOfOrigin
|
|
47
|
+
harmonizedSystemCode
|
|
48
|
+
measurement {
|
|
49
|
+
weight {
|
|
50
|
+
unit
|
|
51
|
+
value
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
locationsCount {
|
|
55
|
+
count
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
`;
|
|
64
|
+
const data = await shopifyClient.request(query, { id: productId });
|
|
65
|
+
if (!data.product) {
|
|
66
|
+
throw new Error(`Product not found: ${productId}`);
|
|
67
|
+
}
|
|
68
|
+
const variants = edgesToNodes(data.product.variants).map((variant) => ({
|
|
69
|
+
variantId: variant.id,
|
|
70
|
+
variantTitle: variant.title,
|
|
71
|
+
variantSku: variant.sku,
|
|
72
|
+
inventoryItem: variant.inventoryItem,
|
|
73
|
+
}));
|
|
74
|
+
return {
|
|
75
|
+
productId: data.product.id,
|
|
76
|
+
productTitle: data.product.title,
|
|
77
|
+
variantsCount: variants.length,
|
|
78
|
+
variants,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
handleToolError("fetch inventory items", error);
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
export { getInventoryItems };
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { gql } from "graphql-request";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { edgesToNodes, handleToolError } from "../lib/toolUtils.js";
|
|
4
|
+
const GetInventoryLevelsInputSchema = z.object({
|
|
5
|
+
inventoryItemId: z
|
|
6
|
+
.string()
|
|
7
|
+
.min(1)
|
|
8
|
+
.describe("The inventory item ID (e.g. gid://shopify/InventoryItem/123 or just 123). Get this from getInventoryItems or product variant data."),
|
|
9
|
+
});
|
|
10
|
+
let shopifyClient;
|
|
11
|
+
const getInventoryLevels = {
|
|
12
|
+
name: "get-inventory-levels",
|
|
13
|
+
description: "Get inventory quantities per location for an inventory item (available, on_hand, committed, reserved, incoming, damaged, etc.)",
|
|
14
|
+
schema: GetInventoryLevelsInputSchema,
|
|
15
|
+
initialize(client) {
|
|
16
|
+
shopifyClient = client;
|
|
17
|
+
},
|
|
18
|
+
execute: async (input) => {
|
|
19
|
+
try {
|
|
20
|
+
const inventoryItemId = input.inventoryItemId.startsWith("gid://")
|
|
21
|
+
? input.inventoryItemId
|
|
22
|
+
: `gid://shopify/InventoryItem/${input.inventoryItemId}`;
|
|
23
|
+
const query = gql `
|
|
24
|
+
#graphql
|
|
25
|
+
|
|
26
|
+
query GetInventoryLevels($id: ID!) {
|
|
27
|
+
inventoryItem(id: $id) {
|
|
28
|
+
id
|
|
29
|
+
sku
|
|
30
|
+
tracked
|
|
31
|
+
inventoryLevels(first: 50) {
|
|
32
|
+
edges {
|
|
33
|
+
node {
|
|
34
|
+
id
|
|
35
|
+
location {
|
|
36
|
+
id
|
|
37
|
+
name
|
|
38
|
+
isActive
|
|
39
|
+
}
|
|
40
|
+
quantities(
|
|
41
|
+
names: [
|
|
42
|
+
"available"
|
|
43
|
+
"on_hand"
|
|
44
|
+
"committed"
|
|
45
|
+
"reserved"
|
|
46
|
+
"incoming"
|
|
47
|
+
"damaged"
|
|
48
|
+
"quality_control"
|
|
49
|
+
"safety_stock"
|
|
50
|
+
]
|
|
51
|
+
) {
|
|
52
|
+
name
|
|
53
|
+
quantity
|
|
54
|
+
}
|
|
55
|
+
updatedAt
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
`;
|
|
62
|
+
const data = await shopifyClient.request(query, {
|
|
63
|
+
id: inventoryItemId,
|
|
64
|
+
});
|
|
65
|
+
if (!data.inventoryItem) {
|
|
66
|
+
throw new Error(`Inventory item not found: ${inventoryItemId}`);
|
|
67
|
+
}
|
|
68
|
+
const levels = edgesToNodes(data.inventoryItem.inventoryLevels);
|
|
69
|
+
return {
|
|
70
|
+
inventoryItemId: data.inventoryItem.id,
|
|
71
|
+
sku: data.inventoryItem.sku,
|
|
72
|
+
tracked: data.inventoryItem.tracked,
|
|
73
|
+
levelsCount: levels.length,
|
|
74
|
+
levels,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
handleToolError("fetch inventory levels", error);
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
export { getInventoryLevels };
|