├── .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 | Shopify MCP server 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 | --------------------------------------------------------------------------------