@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.
Files changed (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +531 -0
  3. package/dist/index.js +107 -0
  4. package/dist/lib/formatters.js +72 -0
  5. package/dist/lib/shopifyAuth.js +96 -0
  6. package/dist/lib/toolUtils.js +36 -0
  7. package/dist/tools/completeDraftOrder.js +76 -0
  8. package/dist/tools/createCustomer.js +142 -0
  9. package/dist/tools/createDraftOrder.js +160 -0
  10. package/dist/tools/createFulfillment.js +100 -0
  11. package/dist/tools/createProduct.js +123 -0
  12. package/dist/tools/createRefund.js +115 -0
  13. package/dist/tools/deleteCustomer.js +47 -0
  14. package/dist/tools/deleteMetafields.js +54 -0
  15. package/dist/tools/deleteProduct.js +43 -0
  16. package/dist/tools/deleteProductVariants.js +82 -0
  17. package/dist/tools/getCollectionById.js +123 -0
  18. package/dist/tools/getCollections.js +88 -0
  19. package/dist/tools/getCustomerById.js +122 -0
  20. package/dist/tools/getCustomerOrders.js +131 -0
  21. package/dist/tools/getCustomers.js +125 -0
  22. package/dist/tools/getFulfillmentOrders.js +106 -0
  23. package/dist/tools/getInventoryItems.js +86 -0
  24. package/dist/tools/getInventoryLevels.js +82 -0
  25. package/dist/tools/getLocations.js +85 -0
  26. package/dist/tools/getMarkets.js +91 -0
  27. package/dist/tools/getMetafieldDefinitions.js +112 -0
  28. package/dist/tools/getMetafields.js +68 -0
  29. package/dist/tools/getOrderById.js +212 -0
  30. package/dist/tools/getOrderRefundDetails.js +131 -0
  31. package/dist/tools/getOrderTransactions.js +85 -0
  32. package/dist/tools/getOrders.js +148 -0
  33. package/dist/tools/getPriceLists.js +92 -0
  34. package/dist/tools/getProductById.js +171 -0
  35. package/dist/tools/getProductVariantsDetailed.js +139 -0
  36. package/dist/tools/getProducts.js +155 -0
  37. package/dist/tools/getShopInfo.js +74 -0
  38. package/dist/tools/manageCustomerAddress.js +149 -0
  39. package/dist/tools/manageProductOptions.js +293 -0
  40. package/dist/tools/manageProductVariants.js +203 -0
  41. package/dist/tools/manageTags.js +79 -0
  42. package/dist/tools/mergeCustomers.js +74 -0
  43. package/dist/tools/orderCancel.js +77 -0
  44. package/dist/tools/orderCloseOpen.js +74 -0
  45. package/dist/tools/orderMarkAsPaid.js +51 -0
  46. package/dist/tools/registry.js +106 -0
  47. package/dist/tools/setInventoryQuantities.js +74 -0
  48. package/dist/tools/setMetafields.js +61 -0
  49. package/dist/tools/updateCustomer.js +119 -0
  50. package/dist/tools/updateOrder.js +131 -0
  51. package/dist/tools/updateProduct.js +132 -0
  52. package/package.json +66 -0
@@ -0,0 +1,155 @@
1
+ import { gql } from "graphql-request";
2
+ import { z } from "zod";
3
+ import { handleToolError } from "../lib/toolUtils.js";
4
+ // Input schema for getProducts
5
+ const GetProductsInputSchema = z.object({
6
+ searchTitle: z.string().optional().describe("Search by title (convenience filter, wraps in title:*...*). Use 'query' for advanced filtering."),
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", "INVENTORY_TOTAL", "PRODUCT_TYPE",
12
+ "PUBLISHED_AT", "RELEVANCE", "TITLE", "UPDATED_AT", "VENDOR"
13
+ ]).optional().describe("Sort key for products"),
14
+ reverse: z.boolean().optional().describe("Reverse the sort order"),
15
+ query: z.string().optional().describe("Raw query string for advanced filtering (e.g. 'status:active vendor:Nike tag:sale')")
16
+ });
17
+ // Will be initialized in index.ts
18
+ let shopifyClient;
19
+ const getProducts = {
20
+ name: "get-products",
21
+ description: "Get all products or search by title",
22
+ schema: GetProductsInputSchema,
23
+ // Add initialize method to set up the GraphQL client
24
+ initialize(client) {
25
+ shopifyClient = client;
26
+ },
27
+ execute: async (input) => {
28
+ try {
29
+ const { searchTitle, limit, after, before, sortKey, reverse, query: rawQuery } = input;
30
+ // Build query string from convenience filters and raw query
31
+ const queryParts = [];
32
+ if (searchTitle) {
33
+ queryParts.push(`title:*${searchTitle}*`);
34
+ }
35
+ if (rawQuery) {
36
+ queryParts.push(rawQuery);
37
+ }
38
+ const queryFilter = queryParts.join(" ") || undefined;
39
+ const query = gql `
40
+ #graphql
41
+
42
+ query GetProducts($first: Int!, $query: String, $after: String, $before: String, $sortKey: ProductSortKeys, $reverse: Boolean) {
43
+ products(first: $first, query: $query, after: $after, before: $before, sortKey: $sortKey, reverse: $reverse) {
44
+ edges {
45
+ node {
46
+ id
47
+ title
48
+ description
49
+ handle
50
+ status
51
+ createdAt
52
+ updatedAt
53
+ totalInventory
54
+ priceRangeV2 {
55
+ minVariantPrice {
56
+ amount
57
+ currencyCode
58
+ }
59
+ maxVariantPrice {
60
+ amount
61
+ currencyCode
62
+ }
63
+ }
64
+ media(first: 1) {
65
+ edges {
66
+ node {
67
+ ... on MediaImage {
68
+ id
69
+ image {
70
+ url
71
+ altText
72
+ }
73
+ }
74
+ }
75
+ }
76
+ }
77
+ variants(first: 5) {
78
+ edges {
79
+ node {
80
+ id
81
+ title
82
+ price
83
+ inventoryQuantity
84
+ sku
85
+ }
86
+ }
87
+ }
88
+ }
89
+ }
90
+ pageInfo {
91
+ hasNextPage
92
+ hasPreviousPage
93
+ startCursor
94
+ endCursor
95
+ }
96
+ }
97
+ }
98
+ `;
99
+ const variables = {
100
+ first: limit,
101
+ query: queryFilter,
102
+ ...(after && { after }),
103
+ ...(before && { before }),
104
+ ...(sortKey && { sortKey }),
105
+ ...(reverse !== undefined && { reverse })
106
+ };
107
+ const data = (await shopifyClient.request(query, variables));
108
+ // Extract and format product data
109
+ const products = data.products.edges.map((edge) => {
110
+ const product = edge.node;
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
+ }));
119
+ // Get first image if it exists
120
+ const firstMedia = product.media.edges.find((e) => e.node.image);
121
+ const imageUrl = firstMedia?.node.image?.url || null;
122
+ return {
123
+ id: product.id,
124
+ title: product.title,
125
+ description: product.description,
126
+ handle: product.handle,
127
+ status: product.status,
128
+ createdAt: product.createdAt,
129
+ updatedAt: product.updatedAt,
130
+ totalInventory: product.totalInventory,
131
+ priceRange: {
132
+ minPrice: {
133
+ amount: product.priceRangeV2.minVariantPrice.amount,
134
+ currencyCode: product.priceRangeV2.minVariantPrice.currencyCode
135
+ },
136
+ maxPrice: {
137
+ amount: product.priceRangeV2.maxVariantPrice.amount,
138
+ currencyCode: product.priceRangeV2.maxVariantPrice.currencyCode
139
+ }
140
+ },
141
+ imageUrl,
142
+ variants
143
+ };
144
+ });
145
+ return {
146
+ products,
147
+ pageInfo: data.products.pageInfo
148
+ };
149
+ }
150
+ catch (error) {
151
+ handleToolError("fetch products", error);
152
+ }
153
+ }
154
+ };
155
+ export { getProducts };
@@ -0,0 +1,74 @@
1
+ import { gql } from "graphql-request";
2
+ import { z } from "zod";
3
+ import { handleToolError } from "../lib/toolUtils.js";
4
+ const GetShopInfoInputSchema = z.object({});
5
+ let shopifyClient;
6
+ const getShopInfo = {
7
+ name: "get-shop-info",
8
+ description: "Get shop configuration including name, plan, currencies, features, payment settings, tax config, and contact info",
9
+ schema: GetShopInfoInputSchema,
10
+ initialize(client) {
11
+ shopifyClient = client;
12
+ },
13
+ execute: async (_input) => {
14
+ try {
15
+ const query = gql `
16
+ #graphql
17
+
18
+ query GetShopInfo {
19
+ shop {
20
+ id
21
+ name
22
+ email
23
+ contactEmail
24
+ myshopifyDomain
25
+ primaryDomain {
26
+ url
27
+ host
28
+ }
29
+ plan {
30
+ publicDisplayName
31
+ partnerDevelopment
32
+ shopifyPlus
33
+ }
34
+ currencyCode
35
+ enabledPresentmentCurrencies
36
+ ianaTimezone
37
+ timezoneAbbreviation
38
+ taxShipping
39
+ taxesIncluded
40
+ setupRequired
41
+ features {
42
+ giftCards
43
+ reports
44
+ storefront
45
+ harmonizedSystemCode
46
+ avalaraAvatax
47
+ sellsSubscriptions
48
+ }
49
+ paymentSettings {
50
+ supportedDigitalWallets
51
+ }
52
+ shopAddress {
53
+ address1
54
+ address2
55
+ city
56
+ province
57
+ provinceCode
58
+ country
59
+ countryCodeV2
60
+ zip
61
+ phone
62
+ }
63
+ }
64
+ }
65
+ `;
66
+ const data = await shopifyClient.request(query);
67
+ return { shop: data.shop };
68
+ }
69
+ catch (error) {
70
+ handleToolError("fetch shop info", error);
71
+ }
72
+ },
73
+ };
74
+ export { getShopInfo };
@@ -0,0 +1,149 @@
1
+ import { gql } from "graphql-request";
2
+ import { z } from "zod";
3
+ import { checkUserErrors, handleToolError } from "../lib/toolUtils.js";
4
+ import { shippingAddressSchema } from "../lib/formatters.js";
5
+ const ManageCustomerAddressInputSchema = z.object({
6
+ customerId: z.string().describe("Customer GID, e.g. gid://shopify/Customer/123"),
7
+ action: z.enum(["create", "update", "delete"]).describe("Action to perform"),
8
+ addressId: z.string().optional().describe("Address GID (required for update and delete)"),
9
+ address: shippingAddressSchema.optional().describe("Address fields (required for create and update)"),
10
+ setAsDefault: z.boolean().optional().describe("Set this address as the customer's default"),
11
+ });
12
+ let shopifyClient;
13
+ const manageCustomerAddress = {
14
+ name: "manage-customer-address",
15
+ description: "Create, update, or delete a customer's mailing address. Can optionally set as default.",
16
+ schema: ManageCustomerAddressInputSchema,
17
+ initialize(client) {
18
+ shopifyClient = client;
19
+ },
20
+ execute: async (input) => {
21
+ try {
22
+ if (input.action === "create") {
23
+ if (!input.address) {
24
+ throw new Error("Address fields are required for create action");
25
+ }
26
+ const query = gql `
27
+ #graphql
28
+
29
+ mutation customerAddressCreate(
30
+ $customerId: ID!
31
+ $address: MailingAddressInput!
32
+ $setAsDefault: Boolean
33
+ ) {
34
+ customerAddressCreate(
35
+ customerId: $customerId
36
+ address: $address
37
+ setAsDefault: $setAsDefault
38
+ ) {
39
+ address {
40
+ id
41
+ address1
42
+ address2
43
+ city
44
+ company
45
+ countryCodeV2
46
+ firstName
47
+ lastName
48
+ phone
49
+ provinceCode
50
+ zip
51
+ }
52
+ userErrors {
53
+ field
54
+ message
55
+ }
56
+ }
57
+ }
58
+ `;
59
+ const data = (await shopifyClient.request(query, {
60
+ customerId: input.customerId,
61
+ address: input.address,
62
+ setAsDefault: input.setAsDefault,
63
+ }));
64
+ checkUserErrors(data.customerAddressCreate.userErrors, "create address");
65
+ return { address: data.customerAddressCreate.address };
66
+ }
67
+ else if (input.action === "update") {
68
+ if (!input.addressId) {
69
+ throw new Error("addressId is required for update action");
70
+ }
71
+ if (!input.address) {
72
+ throw new Error("Address fields are required for update action");
73
+ }
74
+ const query = gql `
75
+ #graphql
76
+
77
+ mutation customerAddressUpdate(
78
+ $customerId: ID!
79
+ $addressId: ID!
80
+ $address: MailingAddressInput!
81
+ $setAsDefault: Boolean
82
+ ) {
83
+ customerAddressUpdate(
84
+ customerId: $customerId
85
+ addressId: $addressId
86
+ address: $address
87
+ setAsDefault: $setAsDefault
88
+ ) {
89
+ address {
90
+ id
91
+ address1
92
+ address2
93
+ city
94
+ company
95
+ countryCodeV2
96
+ firstName
97
+ lastName
98
+ phone
99
+ provinceCode
100
+ zip
101
+ }
102
+ userErrors {
103
+ field
104
+ message
105
+ }
106
+ }
107
+ }
108
+ `;
109
+ const data = (await shopifyClient.request(query, {
110
+ customerId: input.customerId,
111
+ addressId: input.addressId,
112
+ address: input.address,
113
+ setAsDefault: input.setAsDefault,
114
+ }));
115
+ checkUserErrors(data.customerAddressUpdate.userErrors, "update address");
116
+ return { address: data.customerAddressUpdate.address };
117
+ }
118
+ else {
119
+ // delete
120
+ if (!input.addressId) {
121
+ throw new Error("addressId is required for delete action");
122
+ }
123
+ const query = gql `
124
+ #graphql
125
+
126
+ mutation customerAddressDelete($customerId: ID!, $addressId: ID!) {
127
+ customerAddressDelete(customerId: $customerId, addressId: $addressId) {
128
+ deletedAddressId
129
+ userErrors {
130
+ field
131
+ message
132
+ }
133
+ }
134
+ }
135
+ `;
136
+ const data = (await shopifyClient.request(query, {
137
+ customerId: input.customerId,
138
+ addressId: input.addressId,
139
+ }));
140
+ checkUserErrors(data.customerAddressDelete.userErrors, "delete address");
141
+ return { deletedAddressId: data.customerAddressDelete.deletedAddressId };
142
+ }
143
+ }
144
+ catch (error) {
145
+ handleToolError(`${input.action} customer address`, error);
146
+ }
147
+ },
148
+ };
149
+ export { manageCustomerAddress };
@@ -0,0 +1,293 @@
1
+ import { gql } from "graphql-request";
2
+ import { z } from "zod";
3
+ import { checkUserErrors, handleToolError } from "../lib/toolUtils.js";
4
+ // Input schema for manageProductOptions
5
+ const ManageProductOptionsInputSchema = z.object({
6
+ productId: z.string().min(1).describe("Shopify product GID"),
7
+ action: z.enum(["create", "update", "delete"]),
8
+ variantStrategy: z
9
+ .enum(["LEAVE_AS_IS", "CREATE"])
10
+ .optional()
11
+ .describe("Strategy for variant creation when adding options. LEAVE_AS_IS (default) keeps existing variants, CREATE generates new variant combinations."),
12
+ // For create
13
+ options: z
14
+ .array(z.object({
15
+ name: z.string().describe("Option name, e.g. 'Size' or 'Color'"),
16
+ position: z.number().optional().describe("Position of the option (1-based)"),
17
+ values: z
18
+ .array(z.string())
19
+ .optional()
20
+ .describe("Option values, e.g. ['Small', 'Medium', 'Large']"),
21
+ }))
22
+ .optional()
23
+ .describe("Options to create (action=create)"),
24
+ // For update
25
+ optionId: z.string().optional().describe("Option GID to update (action=update)"),
26
+ name: z.string().optional().describe("New name for the option (action=update)"),
27
+ position: z.number().optional().describe("New position (action=update)"),
28
+ valuesToAdd: z
29
+ .array(z.string())
30
+ .optional()
31
+ .describe("Values to add (action=update)"),
32
+ valuesToDelete: z
33
+ .array(z.string())
34
+ .optional()
35
+ .describe("Value GIDs to delete (action=update)"),
36
+ // For delete
37
+ optionIds: z
38
+ .array(z.string())
39
+ .optional()
40
+ .describe("Option GIDs to delete (action=delete)"),
41
+ });
42
+ // Will be initialized in index.ts
43
+ let shopifyClient;
44
+ const manageProductOptions = {
45
+ name: "manage-product-options",
46
+ description: "Create, update, or delete product options (e.g. Size, Color). Use action='create' to add options, 'update' to rename or add/remove values, 'delete' to remove options.",
47
+ schema: ManageProductOptionsInputSchema,
48
+ initialize(client) {
49
+ shopifyClient = client;
50
+ },
51
+ execute: async (input) => {
52
+ try {
53
+ const { productId, action } = input;
54
+ if (action === "create") {
55
+ if (!input.options?.length) {
56
+ throw new Error("options array is required for action=create");
57
+ }
58
+ const query = gql `
59
+ #graphql
60
+
61
+ mutation productOptionsCreate(
62
+ $productId: ID!
63
+ $options: [OptionCreateInput!]!
64
+ $variantStrategy: ProductOptionCreateVariantStrategy
65
+ ) {
66
+ productOptionsCreate(
67
+ productId: $productId
68
+ options: $options
69
+ variantStrategy: $variantStrategy
70
+ ) {
71
+ product {
72
+ ...ProductOptionsFields
73
+ }
74
+ userErrors {
75
+ field
76
+ message
77
+ code
78
+ }
79
+ }
80
+ }
81
+
82
+ fragment ProductOptionsFields on Product {
83
+ id
84
+ title
85
+ options {
86
+ id
87
+ name
88
+ position
89
+ optionValues {
90
+ id
91
+ name
92
+ hasVariants
93
+ }
94
+ }
95
+ variants(first: 20) {
96
+ edges {
97
+ node {
98
+ id
99
+ title
100
+ price
101
+ selectedOptions {
102
+ name
103
+ value
104
+ }
105
+ }
106
+ }
107
+ }
108
+ }
109
+ `;
110
+ const options = input.options.map((o) => ({
111
+ name: o.name,
112
+ ...(o.position !== undefined && { position: o.position }),
113
+ ...(o.values && {
114
+ values: o.values.map((v) => ({ name: v })),
115
+ }),
116
+ }));
117
+ const data = (await shopifyClient.request(query, {
118
+ productId,
119
+ options,
120
+ variantStrategy: input.variantStrategy || "LEAVE_AS_IS",
121
+ }));
122
+ checkUserErrors(data.productOptionsCreate.userErrors, "create options");
123
+ return formatProductResponse(data.productOptionsCreate.product);
124
+ }
125
+ if (action === "update") {
126
+ if (!input.optionId) {
127
+ throw new Error("optionId is required for action=update");
128
+ }
129
+ const query = gql `
130
+ #graphql
131
+
132
+ mutation productOptionUpdate(
133
+ $productId: ID!
134
+ $option: OptionUpdateInput!
135
+ $optionValuesToAdd: [OptionValueCreateInput!]
136
+ $optionValuesToDelete: [ID!]
137
+ ) {
138
+ productOptionUpdate(
139
+ productId: $productId
140
+ option: $option
141
+ optionValuesToAdd: $optionValuesToAdd
142
+ optionValuesToDelete: $optionValuesToDelete
143
+ ) {
144
+ product {
145
+ ...ProductOptionsFields
146
+ }
147
+ userErrors {
148
+ field
149
+ message
150
+ code
151
+ }
152
+ }
153
+ }
154
+
155
+ fragment ProductOptionsFields on Product {
156
+ id
157
+ title
158
+ options {
159
+ id
160
+ name
161
+ position
162
+ optionValues {
163
+ id
164
+ name
165
+ hasVariants
166
+ }
167
+ }
168
+ variants(first: 20) {
169
+ edges {
170
+ node {
171
+ id
172
+ title
173
+ price
174
+ selectedOptions {
175
+ name
176
+ value
177
+ }
178
+ }
179
+ }
180
+ }
181
+ }
182
+ `;
183
+ const option = { id: input.optionId };
184
+ if (input.name)
185
+ option.name = input.name;
186
+ if (input.position !== undefined)
187
+ option.position = input.position;
188
+ const variables = { productId, option };
189
+ if (input.valuesToAdd?.length) {
190
+ variables.optionValuesToAdd = input.valuesToAdd.map((v) => ({
191
+ name: v,
192
+ }));
193
+ }
194
+ if (input.valuesToDelete?.length) {
195
+ variables.optionValuesToDelete = input.valuesToDelete;
196
+ }
197
+ const data = (await shopifyClient.request(query, variables));
198
+ checkUserErrors(data.productOptionUpdate.userErrors, "update option");
199
+ return formatProductResponse(data.productOptionUpdate.product);
200
+ }
201
+ if (action === "delete") {
202
+ if (!input.optionIds?.length) {
203
+ throw new Error("optionIds array is required for action=delete");
204
+ }
205
+ const query = gql `
206
+ #graphql
207
+
208
+ mutation productOptionsDelete(
209
+ $productId: ID!
210
+ $options: [ID!]!
211
+ ) {
212
+ productOptionsDelete(
213
+ productId: $productId
214
+ options: $options
215
+ ) {
216
+ product {
217
+ ...ProductOptionsFields
218
+ }
219
+ userErrors {
220
+ field
221
+ message
222
+ code
223
+ }
224
+ }
225
+ }
226
+
227
+ fragment ProductOptionsFields on Product {
228
+ id
229
+ title
230
+ options {
231
+ id
232
+ name
233
+ position
234
+ optionValues {
235
+ id
236
+ name
237
+ hasVariants
238
+ }
239
+ }
240
+ variants(first: 20) {
241
+ edges {
242
+ node {
243
+ id
244
+ title
245
+ price
246
+ selectedOptions {
247
+ name
248
+ value
249
+ }
250
+ }
251
+ }
252
+ }
253
+ }
254
+ `;
255
+ const data = (await shopifyClient.request(query, {
256
+ productId,
257
+ options: input.optionIds,
258
+ }));
259
+ checkUserErrors(data.productOptionsDelete.userErrors, "delete options");
260
+ return formatProductResponse(data.productOptionsDelete.product);
261
+ }
262
+ throw new Error(`Unknown action: ${action}`);
263
+ }
264
+ catch (error) {
265
+ handleToolError("manage product options", error);
266
+ }
267
+ },
268
+ };
269
+ function formatProductResponse(product) {
270
+ return {
271
+ product: {
272
+ id: product.id,
273
+ title: product.title,
274
+ options: product.options.map((o) => ({
275
+ id: o.id,
276
+ name: o.name,
277
+ position: o.position,
278
+ values: o.optionValues.map((v) => ({
279
+ id: v.id,
280
+ name: v.name,
281
+ hasVariants: v.hasVariants,
282
+ })),
283
+ })),
284
+ variants: product.variants.edges.map((e) => ({
285
+ id: e.node.id,
286
+ title: e.node.title,
287
+ price: e.node.price,
288
+ options: e.node.selectedOptions,
289
+ })),
290
+ },
291
+ };
292
+ }
293
+ export { manageProductOptions };