├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── package-lock.json
├── package.json
├── src
├── index.ts
└── tools
│ ├── getCustomerOrders.ts
│ ├── getCustomers.ts
│ ├── getOrderById.ts
│ ├── getOrders.ts
│ ├── getProductById.ts
│ ├── getProducts.ts
│ ├── updateCustomer.ts
│ └── updateOrder.ts
└── tsconfig.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # Dependencies
2 | node_modules/
3 | npm-debug.log*
4 | yarn-debug.log*
5 | yarn-error.log*
6 |
7 | # Build output
8 | dist/
9 | build/
10 |
11 | # Environment variables
12 | .env
13 | .env.local
14 |
15 | # Editors
16 | .idea/
17 | .vscode/
18 | *.swp
19 | *.swo
20 |
21 | # OS Files
22 | .DS_Store
23 | Thumbs.db
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # Source files
2 | src/
3 |
4 | # Development configs
5 | .vscode/
6 | .editorconfig
7 | .eslintrc.js
8 | .prettierrc
9 | jest.config.js
10 | tsconfig.json
11 |
12 | # Test files
13 | tests/
14 | __tests__/
15 | *.test.ts
16 | *.spec.ts
17 |
18 | # Git files
19 | .git/
20 | .gitignore
21 |
22 | # Misc
23 | node_modules/
24 | npm-debug.log
25 | .DS_Store
26 | .env
27 | .env.example
28 | coverage/
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Shopify MCP Server Contributors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Shopify MCP Server
2 |
3 | (please leave a star if you like!)
4 |
5 | MCP Server for Shopify API, enabling interaction with store data through GraphQL API. This server provides tools for managing products, customers, orders, and more.
6 |
7 | **📦 Package Name: `shopify-mcp`**
8 | **🚀 Command: `shopify-mcp` (NOT `shopify-mcp-server`)**
9 |
10 |
11 |
12 |
13 |
14 | ## Features
15 |
16 | - **Product Management**: Search and retrieve product information
17 | - **Customer Management**: Load customer data and manage customer tags
18 | - **Order Management**: Advanced order querying and filtering
19 | - **GraphQL Integration**: Direct integration with Shopify's GraphQL Admin API
20 | - **Comprehensive Error Handling**: Clear error messages for API and authentication issues
21 |
22 | ## Prerequisites
23 |
24 | 1. Node.js (version 16 or higher)
25 | 2. Shopify Custom App Access Token (see setup instructions below)
26 |
27 | ## Setup
28 |
29 | ### Shopify Access Token
30 |
31 | To use this MCP server, you'll need to create a custom app in your Shopify store:
32 |
33 | 1. From your Shopify admin, go to **Settings** > **Apps and sales channels**
34 | 2. Click **Develop apps** (you may need to enable developer preview first)
35 | 3. Click **Create an app**
36 | 4. Set a name for your app (e.g., "Shopify MCP Server")
37 | 5. Click **Configure Admin API scopes**
38 | 6. Select the following scopes:
39 | - `read_products`, `write_products`
40 | - `read_customers`, `write_customers`
41 | - `read_orders`, `write_orders`
42 | 7. Click **Save**
43 | 8. Click **Install app**
44 | 9. Click **Install** to give the app access to your store data
45 | 10. After installation, you'll see your **Admin API access token**
46 | 11. Copy this token - you'll need it for configuration
47 |
48 | ### Usage with Claude Desktop
49 |
50 | Add this to your `claude_desktop_config.json`:
51 |
52 | ```json
53 | {
54 | "mcpServers": {
55 | "shopify": {
56 | "command": "npx",
57 | "args": [
58 | "shopify-mcp",
59 | "--accessToken",
60 | "",
61 | "--domain",
62 | ".myshopify.com"
63 | ]
64 | }
65 | }
66 | }
67 | ```
68 |
69 | Locations for the Claude Desktop config file:
70 |
71 | - MacOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
72 | - Windows: `%APPDATA%/Claude/claude_desktop_config.json`
73 |
74 | ### Alternative: Run Locally with Environment Variables
75 |
76 | If you prefer to use environment variables instead of command-line arguments:
77 |
78 | 1. Create a `.env` file with your Shopify credentials:
79 |
80 | ```
81 | SHOPIFY_ACCESS_TOKEN=your_access_token
82 | MYSHOPIFY_DOMAIN=your-store.myshopify.com
83 | ```
84 |
85 | 2. Run the server with npx:
86 | ```
87 | npx shopify-mcp
88 | ```
89 |
90 | ### Direct Installation (Optional)
91 |
92 | If you want to install the package globally:
93 |
94 | ```
95 | npm install -g shopify-mcp
96 | ```
97 |
98 | Then run it:
99 |
100 | ```
101 | shopify-mcp --accessToken= --domain=.myshopify.com
102 | ```
103 |
104 | **⚠️ Important:** If you see errors about "SHOPIFY_ACCESS_TOKEN environment variable is required" when using command-line arguments, you might have a different package installed. Make sure you're using `shopify-mcp`, not `shopify-mcp-server`.
105 |
106 | ## Available Tools
107 |
108 | ### Product Management
109 |
110 | 1. `get-products`
111 |
112 | - Get all products or search by title
113 | - Inputs:
114 | - `searchTitle` (optional string): Filter products by title
115 | - `limit` (number): Maximum number of products to return
116 |
117 | 2. `get-product-by-id`
118 | - Get a specific product by ID
119 | - Inputs:
120 | - `productId` (string): ID of the product to retrieve
121 |
122 | ### Customer Management
123 |
124 | 1. `get-customers`
125 |
126 | - Get customers or search by name/email
127 | - Inputs:
128 | - `searchQuery` (optional string): Filter customers by name or email
129 | - `limit` (optional number, default: 10): Maximum number of customers to return
130 |
131 | 2. `update-customer`
132 |
133 | - Update a customer's information
134 | - Inputs:
135 | - `id` (string, required): Shopify customer ID (numeric ID only, like "6276879810626")
136 | - `firstName` (string, optional): Customer's first name
137 | - `lastName` (string, optional): Customer's last name
138 | - `email` (string, optional): Customer's email address
139 | - `phone` (string, optional): Customer's phone number
140 | - `tags` (array of strings, optional): Tags to apply to the customer
141 | - `note` (string, optional): Note about the customer
142 | - `taxExempt` (boolean, optional): Whether the customer is exempt from taxes
143 | - `metafields` (array of objects, optional): Customer metafields for storing additional data
144 |
145 | 3. `get-customer-orders`
146 | - Get orders for a specific customer
147 | - Inputs:
148 | - `customerId` (string, required): Shopify customer ID (numeric ID only, like "6276879810626")
149 | - `limit` (optional number, default: 10): Maximum number of orders to return
150 |
151 | ### Order Management
152 |
153 | 1. `get-orders`
154 |
155 | - Get orders with optional filtering
156 | - Inputs:
157 | - `status` (optional string): Filter by order status
158 | - `limit` (optional number, default: 10): Maximum number of orders to return
159 |
160 | 2. `get-order-by-id`
161 |
162 | - Get a specific order by ID
163 | - Inputs:
164 | - `orderId` (string, required): Full Shopify order ID (e.g., "gid://shopify/Order/6090960994370")
165 |
166 | 3. `update-order`
167 |
168 | - Update an existing order with new information
169 | - Inputs:
170 | - `id` (string, required): Shopify order ID
171 | - `tags` (array of strings, optional): New tags for the order
172 | - `email` (string, optional): Update customer email
173 | - `note` (string, optional): Order notes
174 | - `customAttributes` (array of objects, optional): Custom attributes for the order
175 | - `metafields` (array of objects, optional): Order metafields
176 | - `shippingAddress` (object, optional): Shipping address information
177 |
178 | ## Debugging
179 |
180 | If you encounter issues, check Claude Desktop's MCP logs:
181 |
182 | ```
183 | tail -n 20 -f ~/Library/Logs/Claude/mcp*.log
184 | ```
185 |
186 | ## License
187 |
188 | MIT
189 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "shopify-mcp",
3 | "version": "1.0.7",
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 | "scripts": {
9 | "build": "rimraf dist && tsc",
10 | "start": "node dist/index.js",
11 | "dev": "ts-node --esm src/index.ts",
12 | "test": "jest",
13 | "prepublishOnly": "npm run build",
14 | "lint": "eslint 'src/**/*.ts'",
15 | "clean": "rimraf dist"
16 | },
17 | "keywords": [
18 | "shopify",
19 | "mcp",
20 | "model-context-protocol",
21 | "graphql",
22 | "ai",
23 | "llm",
24 | "claude"
25 | ],
26 | "author": {
27 | "name": "Your Name",
28 | "email": "your.email@example.com",
29 | "url": "https://your-website.com"
30 | },
31 | "license": "MIT",
32 | "dependencies": {
33 | "@modelcontextprotocol/sdk": "^1.8.0",
34 | "dotenv": "^16.0.3",
35 | "graphql": "^16.6.0",
36 | "graphql-request": "^5.1.0",
37 | "minimist": "^1.2.8",
38 | "zod": "^3.21.4"
39 | },
40 | "devDependencies": {
41 | "@types/jest": "^29.5.0",
42 | "@types/minimist": "^1.2.2",
43 | "@types/node": "^18.15.11",
44 | "jest": "^29.5.0",
45 | "rimraf": "^5.0.10",
46 | "ts-jest": "^29.1.0",
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": "https://github.com/GeLi2001/shopify-mcp"
58 | },
59 | "bugs": {
60 | "url": "https://github.com/GeLi2001/shopify-mcp/issues"
61 | },
62 | "homepage": "https://github.com/GeLi2001/shopify-mcp#readme",
63 | "engines": {
64 | "node": ">=18.0.0"
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5 | import dotenv from "dotenv";
6 | import { GraphQLClient } from "graphql-request";
7 | import minimist from "minimist";
8 | import { z } from "zod";
9 |
10 | // Import tools
11 | import { getCustomerOrders } from "./tools/getCustomerOrders.js";
12 | import { getCustomers } from "./tools/getCustomers.js";
13 | import { getOrderById } from "./tools/getOrderById.js";
14 | import { getOrders } from "./tools/getOrders.js";
15 | import { getProductById } from "./tools/getProductById.js";
16 | import { getProducts } from "./tools/getProducts.js";
17 | import { updateCustomer } from "./tools/updateCustomer.js";
18 | import { updateOrder } from "./tools/updateOrder.js";
19 |
20 | // Parse command line arguments
21 | const argv = minimist(process.argv.slice(2));
22 |
23 | // Load environment variables from .env file (if it exists)
24 | dotenv.config();
25 |
26 | // Define environment variables - from command line or .env file
27 | const SHOPIFY_ACCESS_TOKEN =
28 | argv.accessToken || process.env.SHOPIFY_ACCESS_TOKEN;
29 | const MYSHOPIFY_DOMAIN = argv.domain || process.env.MYSHOPIFY_DOMAIN;
30 |
31 | // Store in process.env for backwards compatibility
32 | process.env.SHOPIFY_ACCESS_TOKEN = SHOPIFY_ACCESS_TOKEN;
33 | process.env.MYSHOPIFY_DOMAIN = MYSHOPIFY_DOMAIN;
34 |
35 | // Validate required environment variables
36 | if (!SHOPIFY_ACCESS_TOKEN) {
37 | console.error("Error: SHOPIFY_ACCESS_TOKEN is required.");
38 | console.error("Please provide it via command line argument or .env file.");
39 | console.error(" Command line: --accessToken=your_token");
40 | process.exit(1);
41 | }
42 |
43 | if (!MYSHOPIFY_DOMAIN) {
44 | console.error("Error: MYSHOPIFY_DOMAIN is required.");
45 | console.error("Please provide it via command line argument or .env file.");
46 | console.error(" Command line: --domain=your-store.myshopify.com");
47 | process.exit(1);
48 | }
49 |
50 | // Create Shopify GraphQL client
51 | const shopifyClient = new GraphQLClient(
52 | `https://${MYSHOPIFY_DOMAIN}/admin/api/2023-07/graphql.json`,
53 | {
54 | headers: {
55 | "X-Shopify-Access-Token": SHOPIFY_ACCESS_TOKEN,
56 | "Content-Type": "application/json"
57 | }
58 | }
59 | );
60 |
61 | // Initialize tools with shopifyClient
62 | getProducts.initialize(shopifyClient);
63 | getProductById.initialize(shopifyClient);
64 | getCustomers.initialize(shopifyClient);
65 | getOrders.initialize(shopifyClient);
66 | getOrderById.initialize(shopifyClient);
67 | updateOrder.initialize(shopifyClient);
68 | getCustomerOrders.initialize(shopifyClient);
69 | updateCustomer.initialize(shopifyClient);
70 |
71 | // Set up MCP server
72 | const server = new McpServer({
73 | name: "shopify",
74 | version: "1.0.0",
75 | description:
76 | "MCP Server for Shopify API, enabling interaction with store data through GraphQL API"
77 | });
78 |
79 | // Add tools individually, using their schemas directly
80 | server.tool(
81 | "get-products",
82 | {
83 | searchTitle: z.string().optional(),
84 | limit: z.number().default(10)
85 | },
86 | async (args) => {
87 | const result = await getProducts.execute(args);
88 | return {
89 | content: [{ type: "text", text: JSON.stringify(result) }]
90 | };
91 | }
92 | );
93 |
94 | server.tool(
95 | "get-product-by-id",
96 | {
97 | productId: z.string().min(1)
98 | },
99 | async (args) => {
100 | const result = await getProductById.execute(args);
101 | return {
102 | content: [{ type: "text", text: JSON.stringify(result) }]
103 | };
104 | }
105 | );
106 |
107 | server.tool(
108 | "get-customers",
109 | {
110 | searchQuery: z.string().optional(),
111 | limit: z.number().default(10)
112 | },
113 | async (args) => {
114 | const result = await getCustomers.execute(args);
115 | return {
116 | content: [{ type: "text", text: JSON.stringify(result) }]
117 | };
118 | }
119 | );
120 |
121 | server.tool(
122 | "get-orders",
123 | {
124 | status: z.enum(["any", "open", "closed", "cancelled"]).default("any"),
125 | limit: z.number().default(10)
126 | },
127 | async (args) => {
128 | const result = await getOrders.execute(args);
129 | return {
130 | content: [{ type: "text", text: JSON.stringify(result) }]
131 | };
132 | }
133 | );
134 |
135 | // Add the getOrderById tool
136 | server.tool(
137 | "get-order-by-id",
138 | {
139 | orderId: z.string().min(1)
140 | },
141 | async (args) => {
142 | const result = await getOrderById.execute(args);
143 | return {
144 | content: [{ type: "text", text: JSON.stringify(result) }]
145 | };
146 | }
147 | );
148 |
149 | // Add the updateOrder tool
150 | server.tool(
151 | "update-order",
152 | {
153 | id: z.string().min(1),
154 | tags: z.array(z.string()).optional(),
155 | email: z.string().email().optional(),
156 | note: z.string().optional(),
157 | customAttributes: z
158 | .array(
159 | z.object({
160 | key: z.string(),
161 | value: z.string()
162 | })
163 | )
164 | .optional(),
165 | metafields: z
166 | .array(
167 | z.object({
168 | id: z.string().optional(),
169 | namespace: z.string().optional(),
170 | key: z.string().optional(),
171 | value: z.string(),
172 | type: z.string().optional()
173 | })
174 | )
175 | .optional(),
176 | shippingAddress: z
177 | .object({
178 | address1: z.string().optional(),
179 | address2: z.string().optional(),
180 | city: z.string().optional(),
181 | company: z.string().optional(),
182 | country: z.string().optional(),
183 | firstName: z.string().optional(),
184 | lastName: z.string().optional(),
185 | phone: z.string().optional(),
186 | province: z.string().optional(),
187 | zip: z.string().optional()
188 | })
189 | .optional()
190 | },
191 | async (args) => {
192 | const result = await updateOrder.execute(args);
193 | return {
194 | content: [{ type: "text", text: JSON.stringify(result) }]
195 | };
196 | }
197 | );
198 |
199 | // Add the getCustomerOrders tool
200 | server.tool(
201 | "get-customer-orders",
202 | {
203 | customerId: z
204 | .string()
205 | .regex(/^\d+$/, "Customer ID must be numeric")
206 | .describe("Shopify customer ID, numeric excluding gid prefix"),
207 | limit: z.number().default(10)
208 | },
209 | async (args) => {
210 | const result = await getCustomerOrders.execute(args);
211 | return {
212 | content: [{ type: "text", text: JSON.stringify(result) }]
213 | };
214 | }
215 | );
216 |
217 | // Add the updateCustomer tool
218 | server.tool(
219 | "update-customer",
220 | {
221 | id: z
222 | .string()
223 | .regex(/^\d+$/, "Customer ID must be numeric")
224 | .describe("Shopify customer ID, numeric excluding gid prefix"),
225 | firstName: z.string().optional(),
226 | lastName: z.string().optional(),
227 | email: z.string().email().optional(),
228 | phone: z.string().optional(),
229 | tags: z.array(z.string()).optional(),
230 | note: z.string().optional(),
231 | taxExempt: z.boolean().optional(),
232 | metafields: z
233 | .array(
234 | z.object({
235 | id: z.string().optional(),
236 | namespace: z.string().optional(),
237 | key: z.string().optional(),
238 | value: z.string(),
239 | type: z.string().optional()
240 | })
241 | )
242 | .optional()
243 | },
244 | async (args) => {
245 | const result = await updateCustomer.execute(args);
246 | return {
247 | content: [{ type: "text", text: JSON.stringify(result) }]
248 | };
249 | }
250 | );
251 |
252 | // Start the server
253 | const transport = new StdioServerTransport();
254 | server
255 | .connect(transport)
256 | .then(() => {})
257 | .catch((error: unknown) => {
258 | console.error("Failed to start Shopify MCP Server:", error);
259 | });
260 |
--------------------------------------------------------------------------------
/src/tools/getCustomerOrders.ts:
--------------------------------------------------------------------------------
1 | import type { GraphQLClient } from "graphql-request";
2 | import { gql } from "graphql-request";
3 | import { z } from "zod";
4 |
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 | });
10 |
11 | type GetCustomerOrdersInput = z.infer;
12 |
13 | // Will be initialized in index.ts
14 | let shopifyClient: GraphQLClient;
15 |
16 | const getCustomerOrders = {
17 | name: "get-customer-orders",
18 | description: "Get orders for a specific customer",
19 | schema: GetCustomerOrdersInputSchema,
20 |
21 | // Add initialize method to set up the GraphQL client
22 | initialize(client: GraphQLClient) {
23 | shopifyClient = client;
24 | },
25 |
26 | execute: async (input: GetCustomerOrdersInput) => {
27 | try {
28 | const { customerId, limit } = input;
29 |
30 | // Convert the numeric customer ID to the GID format
31 | const customerGid = `gid://shopify/Customer/${customerId}`;
32 |
33 | // Query to get orders for a specific customer
34 | const query = gql`
35 | query GetCustomerOrders($query: String!, $first: Int!) {
36 | orders(query: $query, first: $first) {
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 | email
73 | }
74 | lineItems(first: 5) {
75 | edges {
76 | node {
77 | id
78 | title
79 | quantity
80 | originalTotalSet {
81 | shopMoney {
82 | amount
83 | currencyCode
84 | }
85 | }
86 | variant {
87 | id
88 | title
89 | sku
90 | }
91 | }
92 | }
93 | }
94 | tags
95 | note
96 | }
97 | }
98 | }
99 | }
100 | `;
101 |
102 | // We use the query parameter to filter orders by customer ID
103 | const variables = {
104 | query: `customer_id:${customerId}`,
105 | first: limit
106 | };
107 |
108 | const data = (await shopifyClient.request(query, variables)) as {
109 | orders: any;
110 | };
111 |
112 | // Extract and format order data
113 | const orders = data.orders.edges.map((edge: any) => {
114 | const order = edge.node;
115 |
116 | // Format line items
117 | const lineItems = order.lineItems.edges.map((lineItemEdge: any) => {
118 | const lineItem = lineItemEdge.node;
119 | return {
120 | id: lineItem.id,
121 | title: lineItem.title,
122 | quantity: lineItem.quantity,
123 | originalTotal: lineItem.originalTotalSet.shopMoney,
124 | variant: lineItem.variant
125 | ? {
126 | id: lineItem.variant.id,
127 | title: lineItem.variant.title,
128 | sku: lineItem.variant.sku
129 | }
130 | : null
131 | };
132 | });
133 |
134 | return {
135 | id: order.id,
136 | name: order.name,
137 | createdAt: order.createdAt,
138 | financialStatus: order.displayFinancialStatus,
139 | fulfillmentStatus: order.displayFulfillmentStatus,
140 | totalPrice: order.totalPriceSet.shopMoney,
141 | subtotalPrice: order.subtotalPriceSet.shopMoney,
142 | totalShippingPrice: order.totalShippingPriceSet.shopMoney,
143 | totalTax: order.totalTaxSet.shopMoney,
144 | customer: order.customer
145 | ? {
146 | id: order.customer.id,
147 | firstName: order.customer.firstName,
148 | lastName: order.customer.lastName,
149 | email: order.customer.email
150 | }
151 | : null,
152 | lineItems,
153 | tags: order.tags,
154 | note: order.note
155 | };
156 | });
157 |
158 | return { orders };
159 | } catch (error) {
160 | console.error("Error fetching customer orders:", error);
161 | throw new Error(
162 | `Failed to fetch customer orders: ${
163 | error instanceof Error ? error.message : String(error)
164 | }`
165 | );
166 | }
167 | }
168 | };
169 |
170 | export { getCustomerOrders };
171 |
--------------------------------------------------------------------------------
/src/tools/getCustomers.ts:
--------------------------------------------------------------------------------
1 | import type { GraphQLClient } from "graphql-request";
2 | import { gql } from "graphql-request";
3 | import { z } from "zod";
4 |
5 | // Input schema for getCustomers
6 | const GetCustomersInputSchema = z.object({
7 | searchQuery: z.string().optional(),
8 | limit: z.number().default(10)
9 | });
10 |
11 | type GetCustomersInput = z.infer;
12 |
13 | // Will be initialized in index.ts
14 | let shopifyClient: GraphQLClient;
15 |
16 | const getCustomers = {
17 | name: "get-customers",
18 | description: "Get customers or search by name/email",
19 | schema: GetCustomersInputSchema,
20 |
21 | // Add initialize method to set up the GraphQL client
22 | initialize(client: GraphQLClient) {
23 | shopifyClient = client;
24 | },
25 |
26 | execute: async (input: GetCustomersInput) => {
27 | try {
28 | const { searchQuery, limit } = input;
29 |
30 | const query = gql`
31 | query GetCustomers($first: Int!, $query: String) {
32 | customers(first: $first, query: $query) {
33 | edges {
34 | node {
35 | id
36 | firstName
37 | lastName
38 | email
39 | phone
40 | createdAt
41 | updatedAt
42 | tags
43 | defaultAddress {
44 | address1
45 | address2
46 | city
47 | provinceCode
48 | zip
49 | country
50 | phone
51 | }
52 | addresses {
53 | address1
54 | address2
55 | city
56 | provinceCode
57 | zip
58 | country
59 | phone
60 | }
61 | amountSpent {
62 | amount
63 | currencyCode
64 | }
65 | numberOfOrders
66 | }
67 | }
68 | }
69 | }
70 | `;
71 |
72 | const variables = {
73 | first: limit,
74 | query: searchQuery
75 | };
76 |
77 | const data = (await shopifyClient.request(query, variables)) as {
78 | customers: any;
79 | };
80 |
81 | // Extract and format customer data
82 | const customers = data.customers.edges.map((edge: any) => {
83 | const customer = edge.node;
84 |
85 | return {
86 | id: customer.id,
87 | firstName: customer.firstName,
88 | lastName: customer.lastName,
89 | email: customer.email,
90 | phone: customer.phone,
91 | createdAt: customer.createdAt,
92 | updatedAt: customer.updatedAt,
93 | tags: customer.tags,
94 | defaultAddress: customer.defaultAddress,
95 | addresses: customer.addresses,
96 | amountSpent: customer.amountSpent,
97 | numberOfOrders: customer.numberOfOrders
98 | };
99 | });
100 |
101 | return { customers };
102 | } catch (error) {
103 | console.error("Error fetching customers:", error);
104 | throw new Error(
105 | `Failed to fetch customers: ${
106 | error instanceof Error ? error.message : String(error)
107 | }`
108 | );
109 | }
110 | }
111 | };
112 |
113 | export { getCustomers };
114 |
--------------------------------------------------------------------------------
/src/tools/getOrderById.ts:
--------------------------------------------------------------------------------
1 | import type { GraphQLClient } from "graphql-request";
2 | import { gql } from "graphql-request";
3 | import { z } from "zod";
4 |
5 | // Input schema for getOrderById
6 | const GetOrderByIdInputSchema = z.object({
7 | orderId: z.string().min(1)
8 | });
9 |
10 | type GetOrderByIdInput = z.infer;
11 |
12 | // Will be initialized in index.ts
13 | let shopifyClient: GraphQLClient;
14 |
15 | const getOrderById = {
16 | name: "get-order-by-id",
17 | description: "Get a specific order by ID",
18 | schema: GetOrderByIdInputSchema,
19 |
20 | // Add initialize method to set up the GraphQL client
21 | initialize(client: GraphQLClient) {
22 | shopifyClient = client;
23 | },
24 |
25 | execute: async (input: GetOrderByIdInput) => {
26 | try {
27 | const { orderId } = input;
28 |
29 | const query = gql`
30 | query GetOrderById($id: ID!) {
31 | order(id: $id) {
32 | id
33 | name
34 | createdAt
35 | displayFinancialStatus
36 | displayFulfillmentStatus
37 | totalPriceSet {
38 | shopMoney {
39 | amount
40 | currencyCode
41 | }
42 | }
43 | subtotalPriceSet {
44 | shopMoney {
45 | amount
46 | currencyCode
47 | }
48 | }
49 | totalShippingPriceSet {
50 | shopMoney {
51 | amount
52 | currencyCode
53 | }
54 | }
55 | totalTaxSet {
56 | shopMoney {
57 | amount
58 | currencyCode
59 | }
60 | }
61 | customer {
62 | id
63 | firstName
64 | lastName
65 | email
66 | phone
67 | }
68 | shippingAddress {
69 | address1
70 | address2
71 | city
72 | provinceCode
73 | zip
74 | country
75 | phone
76 | }
77 | lineItems(first: 20) {
78 | edges {
79 | node {
80 | id
81 | title
82 | quantity
83 | originalTotalSet {
84 | shopMoney {
85 | amount
86 | currencyCode
87 | }
88 | }
89 | variant {
90 | id
91 | title
92 | sku
93 | }
94 | }
95 | }
96 | }
97 | tags
98 | note
99 | metafields(first: 20) {
100 | edges {
101 | node {
102 | id
103 | namespace
104 | key
105 | value
106 | type
107 | }
108 | }
109 | }
110 | }
111 | }
112 | `;
113 |
114 | const variables = {
115 | id: orderId
116 | };
117 |
118 | const data = (await shopifyClient.request(query, variables)) as {
119 | order: any;
120 | };
121 |
122 | if (!data.order) {
123 | throw new Error(`Order with ID ${orderId} not found`);
124 | }
125 |
126 | // Extract and format order data
127 | const order = data.order;
128 |
129 | // Format line items
130 | const lineItems = order.lineItems.edges.map((lineItemEdge: any) => {
131 | const lineItem = lineItemEdge.node;
132 | return {
133 | id: lineItem.id,
134 | title: lineItem.title,
135 | quantity: lineItem.quantity,
136 | originalTotal: lineItem.originalTotalSet.shopMoney,
137 | variant: lineItem.variant
138 | ? {
139 | id: lineItem.variant.id,
140 | title: lineItem.variant.title,
141 | sku: lineItem.variant.sku
142 | }
143 | : null
144 | };
145 | });
146 |
147 | // Format metafields
148 | const metafields = order.metafields.edges.map((metafieldEdge: any) => {
149 | const metafield = metafieldEdge.node;
150 | return {
151 | id: metafield.id,
152 | namespace: metafield.namespace,
153 | key: metafield.key,
154 | value: metafield.value,
155 | type: metafield.type
156 | };
157 | });
158 |
159 | const formattedOrder = {
160 | id: order.id,
161 | name: order.name,
162 | createdAt: order.createdAt,
163 | financialStatus: order.displayFinancialStatus,
164 | fulfillmentStatus: order.displayFulfillmentStatus,
165 | totalPrice: order.totalPriceSet.shopMoney,
166 | subtotalPrice: order.subtotalPriceSet.shopMoney,
167 | totalShippingPrice: order.totalShippingPriceSet.shopMoney,
168 | totalTax: order.totalTaxSet.shopMoney,
169 | customer: order.customer
170 | ? {
171 | id: order.customer.id,
172 | firstName: order.customer.firstName,
173 | lastName: order.customer.lastName,
174 | email: order.customer.email,
175 | phone: order.customer.phone
176 | }
177 | : null,
178 | shippingAddress: order.shippingAddress,
179 | lineItems,
180 | tags: order.tags,
181 | note: order.note,
182 | metafields
183 | };
184 |
185 | return { order: formattedOrder };
186 | } catch (error) {
187 | console.error("Error fetching order by ID:", error);
188 | throw new Error(
189 | `Failed to fetch order: ${
190 | error instanceof Error ? error.message : String(error)
191 | }`
192 | );
193 | }
194 | }
195 | };
196 |
197 | export { getOrderById };
198 |
--------------------------------------------------------------------------------
/src/tools/getOrders.ts:
--------------------------------------------------------------------------------
1 | import type { GraphQLClient } from "graphql-request";
2 | import { gql } from "graphql-request";
3 | import { z } from "zod";
4 |
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 | });
10 |
11 | type GetOrdersInput = z.infer;
12 |
13 | // Will be initialized in index.ts
14 | let shopifyClient: GraphQLClient;
15 |
16 | const getOrders = {
17 | name: "get-orders",
18 | description: "Get orders with optional filtering by status",
19 | schema: GetOrdersInputSchema,
20 |
21 | // Add initialize method to set up the GraphQL client
22 | initialize(client: GraphQLClient) {
23 | shopifyClient = client;
24 | },
25 |
26 | execute: async (input: GetOrdersInput) => {
27 | try {
28 | const { status, limit } = input;
29 |
30 | // Build query filters
31 | let queryFilter = "";
32 | if (status !== "any") {
33 | queryFilter = `status:${status}`;
34 | }
35 |
36 | const query = gql`
37 | query GetOrders($first: Int!, $query: String) {
38 | orders(first: $first, query: $query) {
39 | edges {
40 | node {
41 | id
42 | name
43 | createdAt
44 | displayFinancialStatus
45 | displayFulfillmentStatus
46 | totalPriceSet {
47 | shopMoney {
48 | amount
49 | currencyCode
50 | }
51 | }
52 | subtotalPriceSet {
53 | shopMoney {
54 | amount
55 | currencyCode
56 | }
57 | }
58 | totalShippingPriceSet {
59 | shopMoney {
60 | amount
61 | currencyCode
62 | }
63 | }
64 | totalTaxSet {
65 | shopMoney {
66 | amount
67 | currencyCode
68 | }
69 | }
70 | customer {
71 | id
72 | firstName
73 | lastName
74 | email
75 | }
76 | shippingAddress {
77 | address1
78 | address2
79 | city
80 | provinceCode
81 | zip
82 | country
83 | phone
84 | }
85 | lineItems(first: 10) {
86 | edges {
87 | node {
88 | id
89 | title
90 | quantity
91 | originalTotalSet {
92 | shopMoney {
93 | amount
94 | currencyCode
95 | }
96 | }
97 | variant {
98 | id
99 | title
100 | sku
101 | }
102 | }
103 | }
104 | }
105 | tags
106 | note
107 | }
108 | }
109 | }
110 | }
111 | `;
112 |
113 | const variables = {
114 | first: limit,
115 | query: queryFilter || undefined
116 | };
117 |
118 | const data = (await shopifyClient.request(query, variables)) as {
119 | orders: any;
120 | };
121 |
122 | // Extract and format order data
123 | const orders = data.orders.edges.map((edge: any) => {
124 | const order = edge.node;
125 |
126 | // Format line items
127 | const lineItems = order.lineItems.edges.map((lineItemEdge: any) => {
128 | const lineItem = lineItemEdge.node;
129 | return {
130 | id: lineItem.id,
131 | title: lineItem.title,
132 | quantity: lineItem.quantity,
133 | originalTotal: lineItem.originalTotalSet.shopMoney,
134 | variant: lineItem.variant
135 | ? {
136 | id: lineItem.variant.id,
137 | title: lineItem.variant.title,
138 | sku: lineItem.variant.sku
139 | }
140 | : null
141 | };
142 | });
143 |
144 | return {
145 | id: order.id,
146 | name: order.name,
147 | createdAt: order.createdAt,
148 | financialStatus: order.displayFinancialStatus,
149 | fulfillmentStatus: order.displayFulfillmentStatus,
150 | totalPrice: order.totalPriceSet.shopMoney,
151 | subtotalPrice: order.subtotalPriceSet.shopMoney,
152 | totalShippingPrice: order.totalShippingPriceSet.shopMoney,
153 | totalTax: order.totalTaxSet.shopMoney,
154 | customer: order.customer
155 | ? {
156 | id: order.customer.id,
157 | firstName: order.customer.firstName,
158 | lastName: order.customer.lastName,
159 | email: order.customer.email
160 | }
161 | : null,
162 | shippingAddress: order.shippingAddress,
163 | lineItems,
164 | tags: order.tags,
165 | note: order.note
166 | };
167 | });
168 |
169 | return { orders };
170 | } catch (error) {
171 | console.error("Error fetching orders:", error);
172 | throw new Error(
173 | `Failed to fetch orders: ${
174 | error instanceof Error ? error.message : String(error)
175 | }`
176 | );
177 | }
178 | }
179 | };
180 |
181 | export { getOrders };
182 |
--------------------------------------------------------------------------------
/src/tools/getProductById.ts:
--------------------------------------------------------------------------------
1 | import type { GraphQLClient } from "graphql-request";
2 | import { gql } from "graphql-request";
3 | import { z } from "zod";
4 |
5 | // Input schema for getProductById
6 | const GetProductByIdInputSchema = z.object({
7 | productId: z.string().min(1)
8 | });
9 |
10 | type GetProductByIdInput = z.infer;
11 |
12 | // Will be initialized in index.ts
13 | let shopifyClient: GraphQLClient;
14 |
15 | const getProductById = {
16 | name: "get-product-by-id",
17 | description: "Get a specific product by ID",
18 | schema: GetProductByIdInputSchema,
19 |
20 | // Add initialize method to set up the GraphQL client
21 | initialize(client: GraphQLClient) {
22 | shopifyClient = client;
23 | },
24 |
25 | execute: async (input: GetProductByIdInput) => {
26 | try {
27 | const { productId } = input;
28 |
29 | const query = gql`
30 | query GetProductById($id: ID!) {
31 | product(id: $id) {
32 | id
33 | title
34 | description
35 | handle
36 | status
37 | createdAt
38 | updatedAt
39 | totalInventory
40 | priceRangeV2 {
41 | minVariantPrice {
42 | amount
43 | currencyCode
44 | }
45 | maxVariantPrice {
46 | amount
47 | currencyCode
48 | }
49 | }
50 | images(first: 5) {
51 | edges {
52 | node {
53 | id
54 | url
55 | altText
56 | width
57 | height
58 | }
59 | }
60 | }
61 | variants(first: 20) {
62 | edges {
63 | node {
64 | id
65 | title
66 | price
67 | inventoryQuantity
68 | sku
69 | selectedOptions {
70 | name
71 | value
72 | }
73 | }
74 | }
75 | }
76 | collections(first: 5) {
77 | edges {
78 | node {
79 | id
80 | title
81 | }
82 | }
83 | }
84 | tags
85 | vendor
86 | }
87 | }
88 | `;
89 |
90 | const variables = {
91 | id: productId
92 | };
93 |
94 | const data = (await shopifyClient.request(query, variables)) as {
95 | product: any;
96 | };
97 |
98 | if (!data.product) {
99 | throw new Error(`Product with ID ${productId} not found`);
100 | }
101 |
102 | // Format product data
103 | const product = data.product;
104 |
105 | // Format variants
106 | const variants = product.variants.edges.map((variantEdge: any) => ({
107 | id: variantEdge.node.id,
108 | title: variantEdge.node.title,
109 | price: variantEdge.node.price,
110 | inventoryQuantity: variantEdge.node.inventoryQuantity,
111 | sku: variantEdge.node.sku,
112 | options: variantEdge.node.selectedOptions
113 | }));
114 |
115 | // Format images
116 | const images = product.images.edges.map((imageEdge: any) => ({
117 | id: imageEdge.node.id,
118 | url: imageEdge.node.url,
119 | altText: imageEdge.node.altText,
120 | width: imageEdge.node.width,
121 | height: imageEdge.node.height
122 | }));
123 |
124 | // Format collections
125 | const collections = product.collections.edges.map(
126 | (collectionEdge: any) => ({
127 | id: collectionEdge.node.id,
128 | title: collectionEdge.node.title
129 | })
130 | );
131 |
132 | const formattedProduct = {
133 | id: product.id,
134 | title: product.title,
135 | description: product.description,
136 | handle: product.handle,
137 | status: product.status,
138 | createdAt: product.createdAt,
139 | updatedAt: product.updatedAt,
140 | totalInventory: product.totalInventory,
141 | priceRange: {
142 | minPrice: {
143 | amount: product.priceRangeV2.minVariantPrice.amount,
144 | currencyCode: product.priceRangeV2.minVariantPrice.currencyCode
145 | },
146 | maxPrice: {
147 | amount: product.priceRangeV2.maxVariantPrice.amount,
148 | currencyCode: product.priceRangeV2.maxVariantPrice.currencyCode
149 | }
150 | },
151 | images,
152 | variants,
153 | collections,
154 | tags: product.tags,
155 | vendor: product.vendor
156 | };
157 |
158 | return { product: formattedProduct };
159 | } catch (error) {
160 | console.error("Error fetching product by ID:", error);
161 | throw new Error(
162 | `Failed to fetch product: ${
163 | error instanceof Error ? error.message : String(error)
164 | }`
165 | );
166 | }
167 | }
168 | };
169 |
170 | export { getProductById };
171 |
--------------------------------------------------------------------------------
/src/tools/getProducts.ts:
--------------------------------------------------------------------------------
1 | import type { GraphQLClient } from "graphql-request";
2 | import { gql } from "graphql-request";
3 | import { z } from "zod";
4 |
5 | // Input schema for getProducts
6 | const GetProductsInputSchema = z.object({
7 | searchTitle: z.string().optional(),
8 | limit: z.number().default(10)
9 | });
10 |
11 | type GetProductsInput = z.infer;
12 |
13 | // Will be initialized in index.ts
14 | let shopifyClient: GraphQLClient;
15 |
16 | const getProducts = {
17 | name: "get-products",
18 | description: "Get all products or search by title",
19 | schema: GetProductsInputSchema,
20 |
21 | // Add initialize method to set up the GraphQL client
22 | initialize(client: GraphQLClient) {
23 | shopifyClient = client;
24 | },
25 |
26 | execute: async (input: GetProductsInput) => {
27 | try {
28 | const { searchTitle, limit } = input;
29 |
30 | // Create query based on whether we're searching by title or not
31 | const query = gql`
32 | query GetProducts($first: Int!, $query: String) {
33 | products(first: $first, query: $query) {
34 | edges {
35 | node {
36 | id
37 | title
38 | description
39 | handle
40 | status
41 | createdAt
42 | updatedAt
43 | totalInventory
44 | priceRangeV2 {
45 | minVariantPrice {
46 | amount
47 | currencyCode
48 | }
49 | maxVariantPrice {
50 | amount
51 | currencyCode
52 | }
53 | }
54 | images(first: 1) {
55 | edges {
56 | node {
57 | url
58 | altText
59 | }
60 | }
61 | }
62 | variants(first: 5) {
63 | edges {
64 | node {
65 | id
66 | title
67 | price
68 | inventoryQuantity
69 | sku
70 | }
71 | }
72 | }
73 | }
74 | }
75 | }
76 | }
77 | `;
78 |
79 | const variables = {
80 | first: limit,
81 | query: searchTitle ? `title:*${searchTitle}*` : undefined
82 | };
83 |
84 | const data = (await shopifyClient.request(query, variables)) as {
85 | products: any;
86 | };
87 |
88 | // Extract and format product data
89 | const products = data.products.edges.map((edge: any) => {
90 | const product = edge.node;
91 |
92 | // Format variants
93 | const variants = product.variants.edges.map((variantEdge: any) => ({
94 | id: variantEdge.node.id,
95 | title: variantEdge.node.title,
96 | price: variantEdge.node.price,
97 | inventoryQuantity: variantEdge.node.inventoryQuantity,
98 | sku: variantEdge.node.sku
99 | }));
100 |
101 | // Get first image if it exists
102 | const imageUrl =
103 | product.images.edges.length > 0
104 | ? product.images.edges[0].node.url
105 | : null;
106 |
107 | return {
108 | id: product.id,
109 | title: product.title,
110 | description: product.description,
111 | handle: product.handle,
112 | status: product.status,
113 | createdAt: product.createdAt,
114 | updatedAt: product.updatedAt,
115 | totalInventory: product.totalInventory,
116 | priceRange: {
117 | minPrice: {
118 | amount: product.priceRangeV2.minVariantPrice.amount,
119 | currencyCode: product.priceRangeV2.minVariantPrice.currencyCode
120 | },
121 | maxPrice: {
122 | amount: product.priceRangeV2.maxVariantPrice.amount,
123 | currencyCode: product.priceRangeV2.maxVariantPrice.currencyCode
124 | }
125 | },
126 | imageUrl,
127 | variants
128 | };
129 | });
130 |
131 | return { products };
132 | } catch (error) {
133 | console.error("Error fetching products:", error);
134 | throw new Error(
135 | `Failed to fetch products: ${
136 | error instanceof Error ? error.message : String(error)
137 | }`
138 | );
139 | }
140 | }
141 | };
142 |
143 | export { getProducts };
144 |
--------------------------------------------------------------------------------
/src/tools/updateCustomer.ts:
--------------------------------------------------------------------------------
1 | import type { GraphQLClient } from "graphql-request";
2 | import { gql } from "graphql-request";
3 | import { z } from "zod";
4 |
5 | // Input schema for updating a customer
6 | const UpdateCustomerInputSchema = z.object({
7 | id: z.string().regex(/^\d+$/, "Customer ID must be numeric"),
8 | firstName: z.string().optional(),
9 | lastName: z.string().optional(),
10 | email: z.string().email().optional(),
11 | phone: z.string().optional(),
12 | tags: z.array(z.string()).optional(),
13 | note: z.string().optional(),
14 | // acceptsMarketing field is deprecated as it's not supported in the API
15 | acceptsMarketing: z.boolean().optional(),
16 | taxExempt: z.boolean().optional(),
17 | metafields: z
18 | .array(
19 | z.object({
20 | id: z.string().optional(),
21 | namespace: z.string().optional(),
22 | key: z.string().optional(),
23 | value: z.string(),
24 | type: z.string().optional()
25 | })
26 | )
27 | .optional()
28 | });
29 |
30 | type UpdateCustomerInput = z.infer;
31 |
32 | // Will be initialized in index.ts
33 | let shopifyClient: GraphQLClient;
34 |
35 | const updateCustomer = {
36 | name: "update-customer",
37 | description: "Update a customer's information",
38 | schema: UpdateCustomerInputSchema,
39 |
40 | // Add initialize method to set up the GraphQL client
41 | initialize(client: GraphQLClient) {
42 | shopifyClient = client;
43 | },
44 |
45 | execute: async (input: UpdateCustomerInput) => {
46 | try {
47 | const { id, acceptsMarketing, ...customerFields } = input;
48 |
49 | // Convert numeric ID to GID format
50 | const customerGid = `gid://shopify/Customer/${id}`;
51 |
52 | // Log a warning if acceptsMarketing was provided
53 | if (acceptsMarketing !== undefined) {
54 | console.warn(
55 | "The acceptsMarketing field is not supported by the Shopify API and will be ignored"
56 | );
57 | }
58 |
59 | const query = gql`
60 | mutation customerUpdate($input: CustomerInput!) {
61 | customerUpdate(input: $input) {
62 | customer {
63 | id
64 | firstName
65 | lastName
66 | email
67 | phone
68 | tags
69 | note
70 | taxExempt
71 | metafields(first: 10) {
72 | edges {
73 | node {
74 | id
75 | namespace
76 | key
77 | value
78 | }
79 | }
80 | }
81 | }
82 | userErrors {
83 | field
84 | message
85 | }
86 | }
87 | }
88 | `;
89 |
90 | const variables = {
91 | input: {
92 | id: customerGid,
93 | ...customerFields
94 | }
95 | };
96 |
97 | const data = (await shopifyClient.request(query, variables)) as {
98 | customerUpdate: {
99 | customer: any;
100 | userErrors: Array<{
101 | field: string;
102 | message: string;
103 | }>;
104 | };
105 | };
106 |
107 | // If there are user errors, throw an error
108 | if (data.customerUpdate.userErrors.length > 0) {
109 | throw new Error(
110 | `Failed to update customer: ${data.customerUpdate.userErrors
111 | .map((e) => `${e.field}: ${e.message}`)
112 | .join(", ")}`
113 | );
114 | }
115 |
116 | // Format and return the updated customer
117 | const customer = data.customerUpdate.customer;
118 |
119 | // Format metafields if they exist
120 | const metafields =
121 | customer.metafields?.edges.map((edge: any) => edge.node) || [];
122 |
123 | return {
124 | customer: {
125 | id: customer.id,
126 | firstName: customer.firstName,
127 | lastName: customer.lastName,
128 | email: customer.email,
129 | phone: customer.phone,
130 | tags: customer.tags,
131 | note: customer.note,
132 | taxExempt: customer.taxExempt,
133 | metafields
134 | }
135 | };
136 | } catch (error) {
137 | console.error("Error updating customer:", error);
138 | throw new Error(
139 | `Failed to update customer: ${
140 | error instanceof Error ? error.message : String(error)
141 | }`
142 | );
143 | }
144 | }
145 | };
146 |
147 | export { updateCustomer };
148 |
--------------------------------------------------------------------------------
/src/tools/updateOrder.ts:
--------------------------------------------------------------------------------
1 | import type { GraphQLClient } from "graphql-request";
2 | import { gql } from "graphql-request";
3 | import { z } from "zod";
4 |
5 | // Will be initialized in index.ts
6 | let shopifyClient: GraphQLClient;
7 |
8 | // Input schema for updateOrder
9 | // Based on https://shopify.dev/docs/api/admin-graphql/latest/mutations/orderupdate
10 | const UpdateOrderInputSchema = z.object({
11 | id: z.string().min(1),
12 | tags: z.array(z.string()).optional(),
13 | email: z.string().email().optional(),
14 | note: z.string().optional(),
15 | customAttributes: z
16 | .array(
17 | z.object({
18 | key: z.string(),
19 | value: z.string()
20 | })
21 | )
22 | .optional(),
23 | metafields: z
24 | .array(
25 | z.object({
26 | id: z.string().optional(),
27 | namespace: z.string().optional(),
28 | key: z.string().optional(),
29 | value: z.string(),
30 | type: z.string().optional()
31 | })
32 | )
33 | .optional(),
34 | shippingAddress: z
35 | .object({
36 | address1: z.string().optional(),
37 | address2: z.string().optional(),
38 | city: z.string().optional(),
39 | company: z.string().optional(),
40 | country: z.string().optional(),
41 | firstName: z.string().optional(),
42 | lastName: z.string().optional(),
43 | phone: z.string().optional(),
44 | province: z.string().optional(),
45 | zip: z.string().optional()
46 | })
47 | .optional()
48 | });
49 |
50 | type UpdateOrderInput = z.infer;
51 |
52 | const updateOrder = {
53 | name: "update-order",
54 | description: "Update an existing order with new information",
55 | schema: UpdateOrderInputSchema,
56 |
57 | // Add initialize method to set up the GraphQL client
58 | initialize(client: GraphQLClient) {
59 | shopifyClient = client;
60 | },
61 |
62 | execute: async (input: UpdateOrderInput) => {
63 | try {
64 | // Prepare input for GraphQL mutation
65 | const { id, ...orderFields } = input;
66 |
67 | const query = gql`
68 | mutation orderUpdate($input: OrderInput!) {
69 | orderUpdate(input: $input) {
70 | order {
71 | id
72 | name
73 | email
74 | note
75 | tags
76 | customAttributes {
77 | key
78 | value
79 | }
80 | metafields(first: 10) {
81 | edges {
82 | node {
83 | id
84 | namespace
85 | key
86 | value
87 | }
88 | }
89 | }
90 | shippingAddress {
91 | address1
92 | address2
93 | city
94 | company
95 | country
96 | firstName
97 | lastName
98 | phone
99 | province
100 | zip
101 | }
102 | }
103 | userErrors {
104 | field
105 | message
106 | }
107 | }
108 | }
109 | `;
110 |
111 | const variables = {
112 | input: {
113 | id,
114 | ...orderFields
115 | }
116 | };
117 |
118 | const data = (await shopifyClient.request(query, variables)) as {
119 | orderUpdate: {
120 | order: any;
121 | userErrors: Array<{
122 | field: string;
123 | message: string;
124 | }>;
125 | };
126 | };
127 |
128 | // If there are user errors, throw an error
129 | if (data.orderUpdate.userErrors.length > 0) {
130 | throw new Error(
131 | `Failed to update order: ${data.orderUpdate.userErrors
132 | .map((e) => `${e.field}: ${e.message}`)
133 | .join(", ")}`
134 | );
135 | }
136 |
137 | // Format and return the updated order
138 | const order = data.orderUpdate.order;
139 |
140 | // Return the updated order data
141 | return {
142 | order: {
143 | id: order.id,
144 | name: order.name,
145 | email: order.email,
146 | note: order.note,
147 | tags: order.tags,
148 | customAttributes: order.customAttributes,
149 | metafields:
150 | order.metafields?.edges.map((edge: any) => edge.node) || [],
151 | shippingAddress: order.shippingAddress
152 | }
153 | };
154 | } catch (error) {
155 | console.error("Error updating order:", error);
156 | throw new Error(
157 | `Failed to update order: ${
158 | error instanceof Error ? error.message : String(error)
159 | }`
160 | );
161 | }
162 | }
163 | };
164 |
165 | export { updateOrder };
166 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "module": "NodeNext",
5 | "moduleResolution": "NodeNext",
6 | "outDir": "./dist",
7 | "rootDir": "./src",
8 | "strict": true,
9 | "esModuleInterop": true,
10 | "skipLibCheck": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "resolveJsonModule": true
13 | },
14 | "include": ["src/**/*"],
15 | "exclude": ["node_modules", "**/*.test.ts"]
16 | }
17 |
--------------------------------------------------------------------------------