@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,74 @@
1
+ import { gql } from "graphql-request";
2
+ import { z } from "zod";
3
+ import { checkUserErrors, handleToolError } from "../lib/toolUtils.js";
4
+ const SetInventoryQuantitiesInputSchema = z.object({
5
+ reason: z.string().describe("Reason for the quantity change (e.g. 'correction', 'cycle_count_available', 'received')"),
6
+ name: z.enum(["available", "on_hand"]).describe("Which quantity to set: 'available' or 'on_hand'"),
7
+ quantities: z
8
+ .array(z.object({
9
+ inventoryItemId: z.string().describe("Inventory item GID"),
10
+ locationId: z.string().describe("Location GID"),
11
+ quantity: z.number().describe("Absolute quantity to set"),
12
+ }))
13
+ .min(1)
14
+ .describe("Quantities to set for each inventory item at each location"),
15
+ ignoreCompareQuantity: z.boolean().default(true).describe("Skip compare-and-set check (default true for simplicity)"),
16
+ });
17
+ let shopifyClient;
18
+ const setInventoryQuantities = {
19
+ name: "inventory-set-quantities",
20
+ description: "Set absolute inventory quantities for items at specific locations. Use for inventory corrections, cycle counts, etc.",
21
+ schema: SetInventoryQuantitiesInputSchema,
22
+ initialize(client) {
23
+ shopifyClient = client;
24
+ },
25
+ execute: async (input) => {
26
+ try {
27
+ const query = gql `
28
+ #graphql
29
+
30
+ mutation inventorySetQuantities($input: InventorySetQuantitiesInput!) {
31
+ inventorySetQuantities(input: $input) {
32
+ inventoryAdjustmentGroup {
33
+ reason
34
+ changes {
35
+ name
36
+ delta
37
+ quantityAfterChange
38
+ item {
39
+ id
40
+ sku
41
+ }
42
+ location {
43
+ id
44
+ name
45
+ }
46
+ }
47
+ }
48
+ userErrors {
49
+ field
50
+ message
51
+ code
52
+ }
53
+ }
54
+ }
55
+ `;
56
+ const data = (await shopifyClient.request(query, {
57
+ input: {
58
+ reason: input.reason,
59
+ name: input.name,
60
+ ignoreCompareQuantity: input.ignoreCompareQuantity,
61
+ quantities: input.quantities,
62
+ },
63
+ }));
64
+ checkUserErrors(data.inventorySetQuantities.userErrors, "set inventory quantities");
65
+ return {
66
+ adjustmentGroup: data.inventorySetQuantities.inventoryAdjustmentGroup,
67
+ };
68
+ }
69
+ catch (error) {
70
+ handleToolError("set inventory quantities", error);
71
+ }
72
+ },
73
+ };
74
+ export { setInventoryQuantities };
@@ -0,0 +1,61 @@
1
+ import { gql } from "graphql-request";
2
+ import { z } from "zod";
3
+ import { checkUserErrors, handleToolError } from "../lib/toolUtils.js";
4
+ const SetMetafieldsInputSchema = z.object({
5
+ metafields: z
6
+ .array(z.object({
7
+ ownerId: z.string().describe("GID of the resource (product, order, customer, variant, etc.)"),
8
+ namespace: z.string().optional().describe("Metafield namespace. If omitted, app-reserved namespace is used."),
9
+ key: z.string().describe("Unique identifier within its namespace (2-64 chars)"),
10
+ value: z.string().describe("The value to set (always stored as string)"),
11
+ type: z.string().optional().describe("Metafield type, e.g. 'single_line_text_field', 'json', 'number_integer'. Required if no definition exists."),
12
+ }))
13
+ .min(1)
14
+ .max(25)
15
+ .describe("Metafields to set (max 25). Works on any resource: products, orders, customers, variants, collections, etc."),
16
+ });
17
+ let shopifyClient;
18
+ const setMetafields = {
19
+ name: "set-metafields",
20
+ description: "Set metafields on any Shopify resource (products, orders, customers, variants, collections, etc.). Creates or updates up to 25 metafields atomically.",
21
+ schema: SetMetafieldsInputSchema,
22
+ initialize(client) {
23
+ shopifyClient = client;
24
+ },
25
+ execute: async (input) => {
26
+ try {
27
+ const query = gql `
28
+ #graphql
29
+
30
+ mutation metafieldsSet($metafields: [MetafieldsSetInput!]!) {
31
+ metafieldsSet(metafields: $metafields) {
32
+ metafields {
33
+ id
34
+ namespace
35
+ key
36
+ value
37
+ type
38
+ ownerType
39
+ }
40
+ userErrors {
41
+ field
42
+ message
43
+ code
44
+ }
45
+ }
46
+ }
47
+ `;
48
+ const data = (await shopifyClient.request(query, {
49
+ metafields: input.metafields,
50
+ }));
51
+ checkUserErrors(data.metafieldsSet.userErrors, "set metafields");
52
+ return {
53
+ metafields: data.metafieldsSet.metafields || [],
54
+ };
55
+ }
56
+ catch (error) {
57
+ handleToolError("set metafields", error);
58
+ }
59
+ },
60
+ };
61
+ export { setMetafields };
@@ -0,0 +1,119 @@
1
+ import { gql } from "graphql-request";
2
+ import { z } from "zod";
3
+ import { checkUserErrors, handleToolError } from "../lib/toolUtils.js";
4
+ // Input schema for updating a customer
5
+ const UpdateCustomerInputSchema = z.object({
6
+ id: z.string().regex(/^\d+$/, "Customer ID must be numeric"),
7
+ firstName: z.string().optional(),
8
+ lastName: z.string().optional(),
9
+ email: z.string().email().optional(),
10
+ phone: z.string().optional(),
11
+ tags: z.array(z.string()).optional(),
12
+ note: z.string().optional(),
13
+ emailMarketingConsent: z
14
+ .object({
15
+ marketingState: z.enum(["NOT_SUBSCRIBED", "SUBSCRIBED", "UNSUBSCRIBED", "PENDING"]),
16
+ consentUpdatedAt: z.string().optional(),
17
+ marketingOptInLevel: z.enum(["SINGLE_OPT_IN", "CONFIRMED_OPT_IN", "UNKNOWN"]).optional()
18
+ })
19
+ .optional(),
20
+ taxExempt: z.boolean().optional(),
21
+ metafields: z
22
+ .array(z.object({
23
+ id: z.string().optional(),
24
+ namespace: z.string().optional(),
25
+ key: z.string().optional(),
26
+ value: z.string(),
27
+ type: z.string().optional()
28
+ }))
29
+ .optional()
30
+ });
31
+ // Will be initialized in index.ts
32
+ let shopifyClient;
33
+ const updateCustomer = {
34
+ name: "update-customer",
35
+ description: "Update a customer's information",
36
+ schema: UpdateCustomerInputSchema,
37
+ // Add initialize method to set up the GraphQL client
38
+ initialize(client) {
39
+ shopifyClient = client;
40
+ },
41
+ execute: async (input) => {
42
+ try {
43
+ const { id, ...customerFields } = input;
44
+ // Convert numeric ID to GID format
45
+ const customerGid = `gid://shopify/Customer/${id}`;
46
+ const query = gql `
47
+ #graphql
48
+
49
+ mutation customerUpdate($input: CustomerInput!) {
50
+ customerUpdate(input: $input) {
51
+ customer {
52
+ id
53
+ firstName
54
+ lastName
55
+ defaultEmailAddress {
56
+ emailAddress
57
+ }
58
+ defaultPhoneNumber {
59
+ phoneNumber
60
+ }
61
+ tags
62
+ note
63
+ taxExempt
64
+ emailMarketingConsent {
65
+ marketingState
66
+ consentUpdatedAt
67
+ marketingOptInLevel
68
+ }
69
+ metafields(first: 10) {
70
+ edges {
71
+ node {
72
+ id
73
+ namespace
74
+ key
75
+ value
76
+ }
77
+ }
78
+ }
79
+ }
80
+ userErrors {
81
+ field
82
+ message
83
+ }
84
+ }
85
+ }
86
+ `;
87
+ const variables = {
88
+ input: {
89
+ id: customerGid,
90
+ ...customerFields
91
+ }
92
+ };
93
+ const data = (await shopifyClient.request(query, variables));
94
+ checkUserErrors(data.customerUpdate.userErrors, "update customer");
95
+ // Format and return the updated customer
96
+ const customer = data.customerUpdate.customer;
97
+ // Format metafields if they exist
98
+ const metafields = customer.metafields?.edges.map((edge) => edge.node) || [];
99
+ return {
100
+ customer: {
101
+ id: customer.id,
102
+ firstName: customer.firstName,
103
+ lastName: customer.lastName,
104
+ email: customer.defaultEmailAddress?.emailAddress || null,
105
+ phone: customer.defaultPhoneNumber?.phoneNumber || null,
106
+ tags: customer.tags,
107
+ note: customer.note,
108
+ taxExempt: customer.taxExempt,
109
+ emailMarketingConsent: customer.emailMarketingConsent,
110
+ metafields
111
+ }
112
+ };
113
+ }
114
+ catch (error) {
115
+ handleToolError("update customer", error);
116
+ }
117
+ }
118
+ };
119
+ export { updateCustomer };
@@ -0,0 +1,131 @@
1
+ import { gql } from "graphql-request";
2
+ import { z } from "zod";
3
+ import { checkUserErrors, handleToolError } from "../lib/toolUtils.js";
4
+ // Will be initialized in index.ts
5
+ let shopifyClient;
6
+ // Input schema for updateOrder
7
+ // Based on https://shopify.dev/docs/api/admin-graphql/latest/mutations/orderupdate
8
+ const UpdateOrderInputSchema = z.object({
9
+ id: z.string().min(1),
10
+ tags: z.array(z.string()).optional(),
11
+ email: z.string().email().optional(),
12
+ note: z.string().optional(),
13
+ customAttributes: z
14
+ .array(z.object({
15
+ key: z.string(),
16
+ value: z.string()
17
+ }))
18
+ .optional(),
19
+ metafields: z
20
+ .array(z.object({
21
+ id: z.string().optional(),
22
+ namespace: z.string().optional(),
23
+ key: z.string().optional(),
24
+ value: z.string(),
25
+ type: z.string().optional()
26
+ }))
27
+ .optional(),
28
+ phone: z.string().optional(),
29
+ poNumber: z.string().optional(),
30
+ shippingAddress: z
31
+ .object({
32
+ address1: z.string().optional(),
33
+ address2: z.string().optional(),
34
+ city: z.string().optional(),
35
+ company: z.string().optional(),
36
+ countryCode: z.string().optional(),
37
+ firstName: z.string().optional(),
38
+ lastName: z.string().optional(),
39
+ phone: z.string().optional(),
40
+ provinceCode: z.string().optional(),
41
+ zip: z.string().optional()
42
+ })
43
+ .optional()
44
+ });
45
+ const updateOrder = {
46
+ name: "update-order",
47
+ description: "Update an existing order with new information",
48
+ schema: UpdateOrderInputSchema,
49
+ // Add initialize method to set up the GraphQL client
50
+ initialize(client) {
51
+ shopifyClient = client;
52
+ },
53
+ execute: async (input) => {
54
+ try {
55
+ // Prepare input for GraphQL mutation
56
+ const { id, ...orderFields } = input;
57
+ const query = gql `
58
+ #graphql
59
+
60
+ mutation orderUpdate($input: OrderInput!) {
61
+ orderUpdate(input: $input) {
62
+ order {
63
+ id
64
+ name
65
+ email
66
+ note
67
+ tags
68
+ customAttributes {
69
+ key
70
+ value
71
+ }
72
+ metafields(first: 10) {
73
+ edges {
74
+ node {
75
+ id
76
+ namespace
77
+ key
78
+ value
79
+ }
80
+ }
81
+ }
82
+ shippingAddress {
83
+ address1
84
+ address2
85
+ city
86
+ company
87
+ country
88
+ firstName
89
+ lastName
90
+ phone
91
+ province
92
+ zip
93
+ }
94
+ }
95
+ userErrors {
96
+ field
97
+ message
98
+ }
99
+ }
100
+ }
101
+ `;
102
+ const variables = {
103
+ input: {
104
+ id,
105
+ ...orderFields
106
+ }
107
+ };
108
+ const data = (await shopifyClient.request(query, variables));
109
+ checkUserErrors(data.orderUpdate.userErrors, "update order");
110
+ // Format and return the updated order
111
+ const order = data.orderUpdate.order;
112
+ // Return the updated order data
113
+ return {
114
+ order: {
115
+ id: order.id,
116
+ name: order.name,
117
+ email: order.email,
118
+ note: order.note,
119
+ tags: order.tags,
120
+ customAttributes: order.customAttributes,
121
+ metafields: order.metafields?.edges.map((edge) => edge.node) || [],
122
+ shippingAddress: order.shippingAddress
123
+ }
124
+ };
125
+ }
126
+ catch (error) {
127
+ handleToolError("update order", error);
128
+ }
129
+ }
130
+ };
131
+ export { updateOrder };
@@ -0,0 +1,132 @@
1
+ import { gql } from "graphql-request";
2
+ import { z } from "zod";
3
+ import { checkUserErrors, handleToolError } from "../lib/toolUtils.js";
4
+ // Input schema for updateProduct
5
+ const UpdateProductInputSchema = z.object({
6
+ id: z.string().min(1).describe("Shopify product GID, e.g. gid://shopify/Product/123"),
7
+ title: z.string().optional(),
8
+ descriptionHtml: z.string().optional(),
9
+ handle: z.string().optional().describe("URL slug for the product"),
10
+ vendor: z.string().optional(),
11
+ productType: z.string().optional(),
12
+ tags: z.array(z.string()).optional(),
13
+ status: z.enum(["ACTIVE", "DRAFT", "ARCHIVED"]).optional(),
14
+ seo: z
15
+ .object({
16
+ title: z.string().optional(),
17
+ description: z.string().optional(),
18
+ })
19
+ .optional()
20
+ .describe("SEO title and description for search engines"),
21
+ metafields: z
22
+ .array(z.object({
23
+ id: z.string().optional(),
24
+ namespace: z.string().optional(),
25
+ key: z.string().optional(),
26
+ value: z.string(),
27
+ type: z.string().optional(),
28
+ }))
29
+ .optional(),
30
+ collectionsToJoin: z.array(z.string()).optional().describe("Collection GIDs to add the product to"),
31
+ collectionsToLeave: z.array(z.string()).optional().describe("Collection GIDs to remove the product from"),
32
+ redirectNewHandle: z.boolean().optional().describe("If true, old handle redirects to new handle"),
33
+ });
34
+ // Will be initialized in index.ts
35
+ let shopifyClient;
36
+ const updateProduct = {
37
+ name: "update-product",
38
+ description: "Update an existing product's fields (title, description, status, tags, etc.)",
39
+ schema: UpdateProductInputSchema,
40
+ initialize(client) {
41
+ shopifyClient = client;
42
+ },
43
+ execute: async (input) => {
44
+ try {
45
+ const { id, ...productFields } = input;
46
+ const query = gql `
47
+ #graphql
48
+
49
+ mutation productUpdate($product: ProductUpdateInput!) {
50
+ productUpdate(product: $product) {
51
+ product {
52
+ id
53
+ title
54
+ handle
55
+ descriptionHtml
56
+ vendor
57
+ productType
58
+ status
59
+ tags
60
+ seo {
61
+ title
62
+ description
63
+ }
64
+ metafields(first: 10) {
65
+ edges {
66
+ node {
67
+ id
68
+ namespace
69
+ key
70
+ value
71
+ }
72
+ }
73
+ }
74
+ variants(first: 20) {
75
+ edges {
76
+ node {
77
+ id
78
+ title
79
+ price
80
+ sku
81
+ selectedOptions {
82
+ name
83
+ value
84
+ }
85
+ }
86
+ }
87
+ }
88
+ }
89
+ userErrors {
90
+ field
91
+ message
92
+ }
93
+ }
94
+ }
95
+ `;
96
+ const variables = {
97
+ product: {
98
+ id,
99
+ ...productFields,
100
+ },
101
+ };
102
+ const data = (await shopifyClient.request(query, variables));
103
+ checkUserErrors(data.productUpdate.userErrors, "update product");
104
+ const product = data.productUpdate.product;
105
+ return {
106
+ product: {
107
+ id: product.id,
108
+ title: product.title,
109
+ handle: product.handle,
110
+ descriptionHtml: product.descriptionHtml,
111
+ vendor: product.vendor,
112
+ productType: product.productType,
113
+ status: product.status,
114
+ tags: product.tags,
115
+ seo: product.seo,
116
+ metafields: product.metafields?.edges.map((e) => e.node) || [],
117
+ variants: product.variants?.edges.map((e) => ({
118
+ id: e.node.id,
119
+ title: e.node.title,
120
+ price: e.node.price,
121
+ sku: e.node.sku,
122
+ options: e.node.selectedOptions,
123
+ })) || [],
124
+ },
125
+ };
126
+ }
127
+ catch (error) {
128
+ handleToolError("update product", error);
129
+ }
130
+ },
131
+ };
132
+ export { updateProduct };
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@klaudworks/shopify-mcp",
3
+ "version": "1.1.0",
4
+ "description": "MCP Server for Shopify API, enabling interaction with store data through GraphQL API",
5
+ "main": "dist/index.js",
6
+ "bin": "dist/index.js",
7
+ "type": "module",
8
+ "publishConfig": {
9
+ "access": "public",
10
+ "provenance": true
11
+ },
12
+ "scripts": {
13
+ "build": "rimraf dist && tsc",
14
+ "start": "node dist/index.js",
15
+ "dev": "node --loader ts-node/esm src/index.ts",
16
+ "test": "echo '(no tests yet — placeholder so CI stays green; swap in node --test when tests are added)' && exit 0",
17
+ "prepublishOnly": "npm run build",
18
+ "clean": "rimraf dist"
19
+ },
20
+ "keywords": [
21
+ "shopify",
22
+ "mcp",
23
+ "model-context-protocol",
24
+ "graphql",
25
+ "ai",
26
+ "llm",
27
+ "claude"
28
+ ],
29
+ "author": {
30
+ "name": "Your Name",
31
+ "email": "your.email@example.com",
32
+ "url": "https://your-website.com"
33
+ },
34
+ "license": "MIT",
35
+ "dependencies": {
36
+ "@modelcontextprotocol/sdk": "^1.29.0",
37
+ "dotenv": "^16.0.3",
38
+ "graphql": "^16.6.0",
39
+ "graphql-request": "^7.4.0",
40
+ "minimist": "^1.2.8",
41
+ "zod": "^3.21.4"
42
+ },
43
+ "devDependencies": {
44
+ "@types/minimist": "^1.2.2",
45
+ "@types/node": "^22.10.0",
46
+ "rimraf": "^5.0.10",
47
+ "ts-node": "^10.9.1",
48
+ "typescript": "^5.0.4"
49
+ },
50
+ "files": [
51
+ "dist",
52
+ "README.md",
53
+ "LICENSE"
54
+ ],
55
+ "repository": {
56
+ "type": "git",
57
+ "url": "git+https://github.com/klaudworks/shopify-mcp.git"
58
+ },
59
+ "bugs": {
60
+ "url": "https://github.com/klaudworks/shopify-mcp/issues"
61
+ },
62
+ "homepage": "https://github.com/klaudworks/shopify-mcp#readme",
63
+ "engines": {
64
+ "node": ">=18.0.0"
65
+ }
66
+ }