import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js"; import pg from 'pg'; const server = new Server( { name: "shopify-mcp-server", version: "1.0.0", }, { capabilities: { tools: {}, }, } ); // Helper to get Shopify credentials from Odoo Database async function getShopifyCredentials() { const client = new pg.Client({ host: '172.18.0.2', port: 5432, user: 'odoo', password: 'odoo', database: 'hazard_new' }); try { await client.connect(); const res = await client.query('SELECT access_token, shop_url FROM shopify_config LIMIT 1;'); if (res.rows.length === 0) { throw new Error("No Shopify configuration found in database."); } return { accessToken: res.rows[0].access_token, shopUrl: res.rows[0].shop_url }; } finally { await client.end(); } } // Define available tools server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: "shopify_get_order", description: "Retrieve a complete Shopify order JSON by its order name (e.g., 4854)", inputSchema: { type: "object", properties: { order_name: { type: "string", description: "The order number/name without the # (e.g. 4854)", }, }, required: ["order_name"], }, }, { name: "shopify_get_product_by_sku", description: "Retrieve product variants from Shopify matching a specific SKU", inputSchema: { type: "object", properties: { sku: { type: "string", description: "The SKU of the product variant", }, }, required: ["sku"], }, }, { name: "shopify_get_inventory_by_sku", description: "Retrieve inventory levels across all locations for a specific SKU", inputSchema: { type: "object", properties: { sku: { type: "string", description: "The SKU of the product variant", }, }, required: ["sku"], }, }, { name: "shopify_get_order_transactions", description: "Retrieve the financial transactions (payments/refunds) for an order by its name (e.g., 4854)", inputSchema: { type: "object", properties: { order_name: { type: "string", description: "The order number/name without the # (e.g. 4854)", }, }, required: ["order_name"], }, } ], }; }); // Handle tool execution server.setRequestHandler(CallToolRequestSchema, async (request) => { try { const creds = await getShopifyCredentials(); const headers = { "X-Shopify-Access-Token": creds.accessToken, "Content-Type": "application/json" }; const baseUrl = `https://${creds.shopUrl}/admin/api/2023-04`; switch (request.params.name) { case "shopify_get_order": { const orderName = String(request.params.arguments.order_name).replace('#', ''); const response = await fetch(`${baseUrl}/orders.json?name=${orderName}&status=any`, { headers }); const data = await response.json(); if (!data.orders || data.orders.length === 0) { return { content: [{ type: "text", text: `Order ${orderName} not found.` }], isError: true, }; } return { content: [{ type: "text", text: JSON.stringify(data.orders[0], null, 2) }], }; } case "shopify_get_product_by_sku": { const sku = String(request.params.arguments.sku); const query = ` { productVariants(first: 5, query: "sku:${sku}") { edges { node { id title sku inventoryQuantity product { title } } } } } `; const response = await fetch(`${baseUrl}/graphql.json`, { method: 'POST', headers, body: JSON.stringify({ query }) }); const data = await response.json(); return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }], }; } case "shopify_get_inventory_by_sku": { const sku = String(request.params.arguments.sku); const query = ` { productVariants(first: 5, query: "sku:${sku}") { edges { node { sku inventoryItem { inventoryLevels(first: 10) { edges { node { quantities(names: ["available"]) { name quantity } location { id name } } } } } } } } } `; const response = await fetch(`${baseUrl}/graphql.json`, { method: 'POST', headers, body: JSON.stringify({ query }) }); const data = await response.json(); return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }], }; } case "shopify_get_order_transactions": { const orderName = String(request.params.arguments.order_name).replace('#', ''); // First get the order ID const orderResp = await fetch(`${baseUrl}/orders.json?name=${orderName}&status=any&fields=id,name`, { headers }); const orderData = await orderResp.json(); if (!orderData.orders || orderData.orders.length === 0) { return { content: [{ type: "text", text: `Order ${orderName} not found.` }], isError: true, }; } const orderId = orderData.orders[0].id; const transResp = await fetch(`${baseUrl}/orders/${orderId}/transactions.json`, { headers }); const transData = await transResp.json(); return { content: [{ type: "text", text: JSON.stringify(transData, null, 2) }], }; } default: throw new Error(`Unknown tool: ${request.params.name}`); } } catch (error) { return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true, }; } }); // Run the server using stdio transport async function run() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("Shopify MCP Server running on stdio"); } run().catch((error) => { console.error("Server error:", error); process.exit(1); });