@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,85 @@
1
+ import { gql } from "graphql-request";
2
+ import { z } from "zod";
3
+ import { edgesToNodes, handleToolError } from "../lib/toolUtils.js";
4
+ const GetLocationsInputSchema = z.object({
5
+ includeInactive: z
6
+ .boolean()
7
+ .default(false)
8
+ .optional()
9
+ .describe("Whether to include deactivated locations (default false)"),
10
+ first: z
11
+ .number()
12
+ .min(1)
13
+ .max(100)
14
+ .default(50)
15
+ .optional()
16
+ .describe("Number of locations to return (default 50, max 100)"),
17
+ });
18
+ let shopifyClient;
19
+ const getLocations = {
20
+ name: "get-locations",
21
+ description: "Get all inventory/fulfillment locations with addresses, capabilities, and active status",
22
+ schema: GetLocationsInputSchema,
23
+ initialize(client) {
24
+ shopifyClient = client;
25
+ },
26
+ execute: async (input) => {
27
+ try {
28
+ const query = gql `
29
+ #graphql
30
+
31
+ query GetLocations($first: Int!, $includeInactive: Boolean!) {
32
+ locations(first: $first, includeInactive: $includeInactive) {
33
+ edges {
34
+ node {
35
+ id
36
+ name
37
+ isActive
38
+ isFulfillmentService
39
+ fulfillsOnlineOrders
40
+ shipsInventory
41
+ hasActiveInventory
42
+ hasUnfulfilledOrders
43
+ address {
44
+ address1
45
+ address2
46
+ city
47
+ province
48
+ provinceCode
49
+ country
50
+ countryCode
51
+ zip
52
+ phone
53
+ latitude
54
+ longitude
55
+ }
56
+ fulfillmentService {
57
+ serviceName
58
+ handle
59
+ }
60
+ localPickupSettingsV2 {
61
+ instructions
62
+ pickupTime
63
+ }
64
+ }
65
+ }
66
+ }
67
+ }
68
+ `;
69
+ const variables = {
70
+ first: input.first ?? 50,
71
+ includeInactive: input.includeInactive ?? false,
72
+ };
73
+ const data = await shopifyClient.request(query, variables);
74
+ const locations = edgesToNodes(data.locations);
75
+ return {
76
+ locationsCount: locations.length,
77
+ locations,
78
+ };
79
+ }
80
+ catch (error) {
81
+ handleToolError("fetch locations", error);
82
+ }
83
+ },
84
+ };
85
+ export { getLocations };
@@ -0,0 +1,91 @@
1
+ import { gql } from "graphql-request";
2
+ import { z } from "zod";
3
+ import { edgesToNodes, handleToolError } from "../lib/toolUtils.js";
4
+ const GetMarketsInputSchema = z.object({
5
+ first: z
6
+ .number()
7
+ .min(1)
8
+ .max(50)
9
+ .default(25)
10
+ .optional()
11
+ .describe("Number of markets to return (default 25, max 50)"),
12
+ });
13
+ let shopifyClient;
14
+ const getMarkets = {
15
+ name: "get-markets",
16
+ description: "Get all markets with their regions, currencies, status, and web presence configuration",
17
+ schema: GetMarketsInputSchema,
18
+ initialize(client) {
19
+ shopifyClient = client;
20
+ },
21
+ execute: async (input) => {
22
+ try {
23
+ const query = gql `
24
+ #graphql
25
+
26
+ query GetMarkets($first: Int!) {
27
+ markets(first: $first) {
28
+ edges {
29
+ node {
30
+ id
31
+ name
32
+ handle
33
+ status
34
+ type
35
+ currencySettings {
36
+ baseCurrency {
37
+ currencyCode
38
+ currencyName
39
+ }
40
+ localCurrencies
41
+ }
42
+ webPresences(first: 10) {
43
+ edges {
44
+ node {
45
+ id
46
+ subfolderSuffix
47
+ defaultLocale {
48
+ locale
49
+ name
50
+ primary
51
+ published
52
+ }
53
+ alternateLocales {
54
+ locale
55
+ name
56
+ published
57
+ }
58
+ domain {
59
+ id
60
+ host
61
+ url
62
+ }
63
+ }
64
+ }
65
+ }
66
+ }
67
+ }
68
+ }
69
+ }
70
+ `;
71
+ const variables = {
72
+ first: input.first ?? 25,
73
+ };
74
+ const data = await shopifyClient.request(query, variables);
75
+ const markets = edgesToNodes(data.markets).map((market) => ({
76
+ ...market,
77
+ webPresences: market.webPresences
78
+ ? edgesToNodes(market.webPresences)
79
+ : [],
80
+ }));
81
+ return {
82
+ marketsCount: markets.length,
83
+ markets,
84
+ };
85
+ }
86
+ catch (error) {
87
+ handleToolError("fetch markets", error);
88
+ }
89
+ },
90
+ };
91
+ export { getMarkets };
@@ -0,0 +1,112 @@
1
+ import { gql } from "graphql-request";
2
+ import { z } from "zod";
3
+ import { edgesToNodes, handleToolError } from "../lib/toolUtils.js";
4
+ /** Map common underscore aliases to their correct Shopify API enum values */
5
+ const OWNER_TYPE_NORMALIZE = {
6
+ PRODUCT_VARIANT: "PRODUCTVARIANT",
7
+ DRAFT_ORDER: "DRAFTORDER",
8
+ CART_TRANSFORM: "CARTTRANSFORM",
9
+ };
10
+ const GetMetafieldDefinitionsInputSchema = z.object({
11
+ ownerType: z
12
+ .enum([
13
+ "API_PERMISSION",
14
+ "ARTICLE",
15
+ "BLOG",
16
+ "CART_TRANSFORM",
17
+ "CARTTRANSFORM",
18
+ "COLLECTION",
19
+ "COMPANY",
20
+ "COMPANY_LOCATION",
21
+ "CUSTOMER",
22
+ "DELIVERY_CUSTOMIZATION",
23
+ "DISCOUNT",
24
+ "DRAFT_ORDER",
25
+ "DRAFTORDER",
26
+ "FULFILLMENT_CONSTRAINT_RULE",
27
+ "GIFT_CARD_TRANSACTION",
28
+ "LOCATION",
29
+ "MARKET",
30
+ "MEDIA_IMAGE",
31
+ "ORDER",
32
+ "ORDER_ROUTING_LOCATION_RULE",
33
+ "PAGE",
34
+ "PAYMENT_CUSTOMIZATION",
35
+ "PRODUCT",
36
+ "PRODUCT_VARIANT",
37
+ "PRODUCTVARIANT",
38
+ "SELLING_PLAN",
39
+ "SHOP",
40
+ "VALIDATION",
41
+ ])
42
+ .describe("The resource type to get metafield definitions for (e.g. PRODUCT, ORDER, CUSTOMER). " +
43
+ "Note: Some Shopify types use concatenated names without underscores " +
44
+ "(PRODUCTVARIANT not PRODUCT_VARIANT, DRAFTORDER not DRAFT_ORDER, CARTTRANSFORM not CART_TRANSFORM). " +
45
+ "Underscore aliases are accepted and normalized automatically."),
46
+ first: z
47
+ .number()
48
+ .min(1)
49
+ .max(100)
50
+ .default(50)
51
+ .optional()
52
+ .describe("Number of definitions to return (default 50, max 100)"),
53
+ });
54
+ let shopifyClient;
55
+ const getMetafieldDefinitions = {
56
+ name: "get-metafield-definitions",
57
+ description: "Discover custom metafield definitions for any resource type (PRODUCT, ORDER, CUSTOMER, etc.). Returns namespace, key, name, type, and validations.",
58
+ schema: GetMetafieldDefinitionsInputSchema,
59
+ initialize(client) {
60
+ shopifyClient = client;
61
+ },
62
+ execute: async (input) => {
63
+ try {
64
+ const query = gql `
65
+ #graphql
66
+
67
+ query GetMetafieldDefinitions(
68
+ $ownerType: MetafieldOwnerType!
69
+ $first: Int!
70
+ ) {
71
+ metafieldDefinitions(ownerType: $ownerType, first: $first) {
72
+ edges {
73
+ node {
74
+ id
75
+ namespace
76
+ key
77
+ name
78
+ description
79
+ ownerType
80
+ pinnedPosition
81
+ type {
82
+ name
83
+ category
84
+ }
85
+ validations {
86
+ name
87
+ type
88
+ value
89
+ }
90
+ }
91
+ }
92
+ }
93
+ }
94
+ `;
95
+ const variables = {
96
+ ownerType: OWNER_TYPE_NORMALIZE[input.ownerType] ?? input.ownerType,
97
+ first: input.first ?? 50,
98
+ };
99
+ const data = await shopifyClient.request(query, variables);
100
+ const definitions = edgesToNodes(data.metafieldDefinitions);
101
+ return {
102
+ ownerType: input.ownerType,
103
+ definitionsCount: definitions.length,
104
+ definitions,
105
+ };
106
+ }
107
+ catch (error) {
108
+ handleToolError("fetch metafield definitions", error);
109
+ }
110
+ },
111
+ };
112
+ export { getMetafieldDefinitions };
@@ -0,0 +1,68 @@
1
+ import { gql } from "graphql-request";
2
+ import { z } from "zod";
3
+ import { handleToolError } from "../lib/toolUtils.js";
4
+ const GetMetafieldsInputSchema = z.object({
5
+ ownerId: z.string().describe("GID of the resource (product, order, customer, variant, collection, etc.)"),
6
+ namespace: z.string().optional().describe("Filter metafields by namespace"),
7
+ first: z.number().default(25).describe("Number of metafields to return (max 50)"),
8
+ after: z.string().optional().describe("Cursor for pagination"),
9
+ });
10
+ let shopifyClient;
11
+ const getMetafields = {
12
+ name: "get-metafields",
13
+ description: "Get metafields for any Shopify resource (products, orders, customers, variants, collections, etc.). Uses the node query with HasMetafields interface.",
14
+ schema: GetMetafieldsInputSchema,
15
+ initialize(client) {
16
+ shopifyClient = client;
17
+ },
18
+ execute: async (input) => {
19
+ try {
20
+ const query = gql `
21
+ #graphql
22
+
23
+ query GetMetafields($ownerId: ID!, $first: Int!, $namespace: String, $after: String) {
24
+ node(id: $ownerId) {
25
+ ... on HasMetafields {
26
+ metafields(first: $first, namespace: $namespace, after: $after) {
27
+ edges {
28
+ node {
29
+ id
30
+ namespace
31
+ key
32
+ value
33
+ type
34
+ updatedAt
35
+ }
36
+ }
37
+ pageInfo {
38
+ hasNextPage
39
+ endCursor
40
+ }
41
+ }
42
+ }
43
+ }
44
+ }
45
+ `;
46
+ const data = (await shopifyClient.request(query, {
47
+ ownerId: input.ownerId,
48
+ first: input.first,
49
+ ...(input.namespace && { namespace: input.namespace }),
50
+ ...(input.after && { after: input.after }),
51
+ }));
52
+ if (!data.node) {
53
+ throw new Error(`Resource with ID ${input.ownerId} not found`);
54
+ }
55
+ if (!data.node.metafields) {
56
+ throw new Error(`Resource ${input.ownerId} does not support metafields`);
57
+ }
58
+ return {
59
+ metafields: data.node.metafields.edges.map((e) => e.node),
60
+ pageInfo: data.node.metafields.pageInfo,
61
+ };
62
+ }
63
+ catch (error) {
64
+ handleToolError("fetch metafields", error);
65
+ }
66
+ },
67
+ };
68
+ export { getMetafields };
@@ -0,0 +1,212 @@
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 getOrderById
6
+ const GetOrderByIdInputSchema = z.object({
7
+ orderId: z.string().min(1)
8
+ });
9
+ // Will be initialized in index.ts
10
+ let shopifyClient;
11
+ const getOrderById = {
12
+ name: "get-order-by-id",
13
+ description: "Get a specific order by ID",
14
+ schema: GetOrderByIdInputSchema,
15
+ // Add initialize method to set up the GraphQL client
16
+ initialize(client) {
17
+ shopifyClient = client;
18
+ },
19
+ execute: async (input) => {
20
+ try {
21
+ const { orderId } = input;
22
+ // Smart lookup: detect format and resolve to GID
23
+ let resolvedId;
24
+ const trimmed = orderId.trim();
25
+ if (trimmed.startsWith("gid://")) {
26
+ // Already a full GID
27
+ resolvedId = trimmed;
28
+ }
29
+ else if (/^#?\d{1,9}$/.test(trimmed)) {
30
+ // Short number or #number — treat as order name, query by name
31
+ const orderName = trimmed.startsWith("#") ? trimmed : `#${trimmed}`;
32
+ const nameQuery = gql `
33
+ #graphql
34
+
35
+ query FindOrderByName($query: String!) {
36
+ orders(first: 1, query: $query) {
37
+ edges {
38
+ node {
39
+ id
40
+ }
41
+ }
42
+ }
43
+ }
44
+ `;
45
+ const nameData = (await shopifyClient.request(nameQuery, {
46
+ query: `name:${orderName}`,
47
+ }));
48
+ if (nameData.orders.edges.length === 0) {
49
+ throw new Error(`Order with name ${orderName} not found`);
50
+ }
51
+ resolvedId = nameData.orders.edges[0].node.id;
52
+ }
53
+ else if (/^\d+$/.test(trimmed)) {
54
+ // Long numeric ID — convert to GID
55
+ resolvedId = `gid://shopify/Order/${trimmed}`;
56
+ }
57
+ else {
58
+ // Unknown format — try as-is
59
+ resolvedId = trimmed;
60
+ }
61
+ const query = gql `
62
+ #graphql
63
+
64
+ query GetOrderById($id: ID!) {
65
+ order(id: $id) {
66
+ id
67
+ name
68
+ createdAt
69
+ displayFinancialStatus
70
+ displayFulfillmentStatus
71
+ totalPriceSet {
72
+ shopMoney {
73
+ amount
74
+ currencyCode
75
+ }
76
+ }
77
+ subtotalPriceSet {
78
+ shopMoney {
79
+ amount
80
+ currencyCode
81
+ }
82
+ }
83
+ totalShippingPriceSet {
84
+ shopMoney {
85
+ amount
86
+ currencyCode
87
+ }
88
+ }
89
+ totalTaxSet {
90
+ shopMoney {
91
+ amount
92
+ currencyCode
93
+ }
94
+ }
95
+ customer {
96
+ id
97
+ firstName
98
+ lastName
99
+ defaultEmailAddress {
100
+ emailAddress
101
+ }
102
+ defaultPhoneNumber {
103
+ phoneNumber
104
+ }
105
+ }
106
+ shippingAddress {
107
+ address1
108
+ address2
109
+ city
110
+ provinceCode
111
+ zip
112
+ country
113
+ phone
114
+ }
115
+ lineItems(first: 20) {
116
+ edges {
117
+ node {
118
+ id
119
+ title
120
+ quantity
121
+ originalTotalSet {
122
+ shopMoney {
123
+ amount
124
+ currencyCode
125
+ }
126
+ }
127
+ variant {
128
+ id
129
+ title
130
+ sku
131
+ }
132
+ }
133
+ }
134
+ }
135
+ tags
136
+ note
137
+ billingAddress {
138
+ address1
139
+ address2
140
+ city
141
+ provinceCode
142
+ zip
143
+ country
144
+ company
145
+ phone
146
+ firstName
147
+ lastName
148
+ }
149
+ cancelReason
150
+ cancelledAt
151
+ updatedAt
152
+ returnStatus
153
+ processedAt
154
+ poNumber
155
+ discountCodes
156
+ currentTotalPriceSet {
157
+ shopMoney {
158
+ amount
159
+ currencyCode
160
+ }
161
+ }
162
+ metafields(first: 20) {
163
+ edges {
164
+ node {
165
+ id
166
+ namespace
167
+ key
168
+ value
169
+ type
170
+ }
171
+ }
172
+ }
173
+ }
174
+ }
175
+ `;
176
+ const variables = {
177
+ id: resolvedId
178
+ };
179
+ const data = (await shopifyClient.request(query, variables));
180
+ if (!data.order) {
181
+ throw new Error(`Order with ID ${orderId} not found`);
182
+ }
183
+ // Extract and format order data
184
+ const order = data.order;
185
+ const base = formatOrderSummary(order);
186
+ const formattedOrder = {
187
+ ...base,
188
+ customer: order.customer
189
+ ? {
190
+ ...base.customer,
191
+ phone: order.customer.defaultPhoneNumber?.phoneNumber || null,
192
+ }
193
+ : null,
194
+ billingAddress: order.billingAddress,
195
+ cancelReason: order.cancelReason,
196
+ cancelledAt: order.cancelledAt,
197
+ updatedAt: order.updatedAt,
198
+ returnStatus: order.returnStatus,
199
+ processedAt: order.processedAt,
200
+ poNumber: order.poNumber,
201
+ discountCodes: order.discountCodes,
202
+ currentTotalPrice: order.currentTotalPriceSet?.shopMoney,
203
+ metafields: edgesToNodes(order.metafields),
204
+ };
205
+ return { order: formattedOrder };
206
+ }
207
+ catch (error) {
208
+ handleToolError("fetch order", error);
209
+ }
210
+ }
211
+ };
212
+ export { getOrderById };
@@ -0,0 +1,131 @@
1
+ import { gql } from "graphql-request";
2
+ import { z } from "zod";
3
+ import { edgesToNodes, handleToolError } from "../lib/toolUtils.js";
4
+ const GetOrderRefundDetailsInputSchema = 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 getOrderRefundDetails = {
12
+ name: "get-order-refund-details",
13
+ description: "Get detailed refund info for an order including refunded items, amounts, restock status, and associated transactions",
14
+ schema: GetOrderRefundDetailsInputSchema,
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 GetOrderRefundDetails($id: ID!) {
27
+ order(id: $id) {
28
+ id
29
+ name
30
+ refunds(first: 25) {
31
+ id
32
+ note
33
+ createdAt
34
+ updatedAt
35
+ totalRefundedSet {
36
+ shopMoney {
37
+ amount
38
+ currencyCode
39
+ }
40
+ presentmentMoney {
41
+ amount
42
+ currencyCode
43
+ }
44
+ }
45
+ refundLineItems(first: 50) {
46
+ edges {
47
+ node {
48
+ quantity
49
+ restockType
50
+ restocked
51
+ priceSet {
52
+ shopMoney {
53
+ amount
54
+ currencyCode
55
+ }
56
+ }
57
+ subtotalSet {
58
+ shopMoney {
59
+ amount
60
+ currencyCode
61
+ }
62
+ }
63
+ totalTaxSet {
64
+ shopMoney {
65
+ amount
66
+ currencyCode
67
+ }
68
+ }
69
+ lineItem {
70
+ id
71
+ title
72
+ sku
73
+ }
74
+ location {
75
+ id
76
+ name
77
+ }
78
+ }
79
+ }
80
+ }
81
+ transactions(first: 10) {
82
+ edges {
83
+ node {
84
+ id
85
+ kind
86
+ status
87
+ gateway
88
+ amountSet {
89
+ shopMoney {
90
+ amount
91
+ currencyCode
92
+ }
93
+ }
94
+ processedAt
95
+ }
96
+ }
97
+ }
98
+ duties {
99
+ amountSet {
100
+ shopMoney {
101
+ amount
102
+ currencyCode
103
+ }
104
+ }
105
+ }
106
+ }
107
+ }
108
+ }
109
+ `;
110
+ const data = await shopifyClient.request(query, { id: orderId });
111
+ if (!data.order) {
112
+ throw new Error(`Order not found: ${orderId}`);
113
+ }
114
+ const refunds = data.order.refunds.map((refund) => ({
115
+ ...refund,
116
+ refundLineItems: edgesToNodes(refund.refundLineItems),
117
+ transactions: edgesToNodes(refund.transactions),
118
+ }));
119
+ return {
120
+ orderId: data.order.id,
121
+ orderName: data.order.name,
122
+ refundsCount: refunds.length,
123
+ refunds,
124
+ };
125
+ }
126
+ catch (error) {
127
+ handleToolError("fetch order refund details", error);
128
+ }
129
+ },
130
+ };
131
+ export { getOrderRefundDetails };