250 lines
7.1 KiB
JavaScript
250 lines
7.1 KiB
JavaScript
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);
|
|
});
|