UNPKG

@mayurgodhani/ecomtools-cli

Version:

E-commerce tools MCP server for Shopify development

287 lines (248 loc) 8.63 kB
/** * E-commerce Tools MCP Server * Server implementation for ecomTools Model Context Protocol */ const { McpServer, ResourceTemplate } = require("@modelcontextprotocol/sdk/server/mcp.js"); const { StdioServerTransport } = require("@modelcontextprotocol/sdk/server/stdio.js"); const { z } = require("zod"); const fs = require('fs'); const path = require('path'); const { parseCodeFile, loadCodeFiles, findMatchingSnippet } = require('./utils/files'); // Create and initialize the MCP server async function createServer() { // Get data directory path, allowing for both local development and installed package const dataDir = fs.existsSync(path.join(__dirname, '../data')) ? path.join(__dirname, '../data') : path.join(__dirname, '../node_modules/ecomtools-cli/data'); console.log(`Loading code snippets from ${dataDir}`); // Ensure the data directory exists if (!fs.existsSync(dataDir)) { fs.mkdirSync(dataDir, { recursive: true }); } // Load code snippets from the data directory const codeSnippets = await loadCodeFiles(dataDir); console.log(`Loaded ${codeSnippets.length} code snippets`); const server = new McpServer({ name: "ecomTools", version: "1.0.0", description: "E-commerce tools and utilities for Shopify development" }); // ----------- TOOLS ----------- // Calculate discount price server.tool( "calculateDiscount", { price: z.number().describe("Original price"), discountPercent: z.number().describe("Discount percentage (0-100)") }, async ({ price, discountPercent }) => { const discountAmount = price * (discountPercent / 100); const finalPrice = price - discountAmount; return { content: [{ type: "text", text: `Original price: $${price.toFixed(2)}\nDiscount: ${discountPercent}% ($${discountAmount.toFixed(2)})\nFinal price: $${finalPrice.toFixed(2)}` }] }; } ); // Generate product JSON-LD schema server.tool( "generateProductSchema", { name: z.string().describe("Product name"), description: z.string().describe("Product description"), price: z.number().describe("Product price"), currency: z.string().default("USD").describe("Currency code"), image: z.string().optional().describe("Product image URL") }, async ({ name, description, price, currency, image }) => { const schema = { "@context": "https://schema.org/", "@type": "Product", "name": name, "description": description, "offers": { "@type": "Offer", "price": price, "priceCurrency": currency } }; if (image) { schema.image = image; } return { content: [{ type: "text", text: `<script type="application/ld+json">\n${JSON.stringify(schema, null, 2)}\n</script>` }] }; } ); // Find code snippets server.tool( "findCodeSnippet", { keywords: z.string().describe("Keywords to search for in the code snippets") }, async ({ keywords }) => { const snippet = findMatchingSnippet(codeSnippets, keywords); if (snippet) { return { content: [{ type: "text", text: `# ${snippet.title}\n\n${snippet.description}\n\n\`\`\`\n${snippet.code}\n\`\`\`` }] }; } else { return { content: [{ type: "text", text: `No code snippets found matching "${keywords}". Try different keywords or check available code snippets.` }] }; } } ); // Generate code server.tool( "generateCode", { type: z.string().describe("Type of code to generate (e.g., cart-drawer, product-quick-view)"), requirements: z.string().describe("Custom requirements or modifications for the code") }, async ({ type, requirements }) => { // Load snippet based on type const typeKeyword = type.toLowerCase().replace(/[^a-z0-9]/g, '-'); const snippet = findMatchingSnippet(codeSnippets, typeKeyword); if (!snippet) { return { content: [{ type: "text", text: `No code template found for "${type}". Available types include: cart-drawer, product-card, etc.` }] }; } // Return the code with a header showing the original type and requirements return { content: [{ type: "text", text: `# Generated Shopify ${type} Code\n\nBased on your requirements: ${requirements}\n\n\`\`\`\n${snippet.code}\n\`\`\`\n\nTo customize this code based on your requirements:\n\n1. Follow the structure above\n2. Modify the styling to match your brand\n3. Adjust the functionality based on your specific needs` }] }; } ); // Shopify Liquid cheatsheet server.resource( "liquid-cheatsheet", "liquid://cheatsheet", async (uri) => ({ contents: [{ uri: uri.href, text: `# Shopify Liquid Cheatsheet ## Output - {{ variable }} - Output a variable - {{ variable | filter }} - Apply a filter to a variable ## Common Filters - {{ "hello" | capitalize }} → "Hello" - {{ "hello world" | split: " " }} → ["hello", "world"] - {{ product.price | money }} → "$10.00" - {{ "2022-01-01" | date: "%B %d, %Y" }} → "January 01, 2022" ## Control Flow - {% if condition %}...{% endif %} - {% if condition %}...{% else %}...{% endif %} - {% if condition %}...{% elsif other_condition %}...{% endif %} - {% unless condition %}...{% endunless %} - {% case variable %}{% when value %}...{% endcase %} ## Loops - {% for product in collection.products %}...{% endfor %} - {% for i in (1..5) %}...{% endfor %} - {% break %} - {% continue %} ## Variables - {% assign variable = value %} - {% capture my_variable %}...{% endcapture %} - {% increment variable %} - {% decrement variable %} ## Schema Tags - {% schema %}...{% endschema %} - {% javascript %}...{% endjavascript %} - {% stylesheet %}...{% endstylesheet %}` }] }) ); // Shopify theme structure guide server.resource( "theme-structure", "theme://structure", async (uri) => ({ contents: [{ uri: uri.href, text: `# Shopify Theme Structure Guide ## Core Files & Directories ### Templates The \`templates/\` directory contains Liquid templates for different page types: - \`templates/index.liquid\` - Home page - \`templates/product.liquid\` - Product pages - \`templates/collection.liquid\` - Collection pages - \`templates/cart.liquid\` - Cart page - \`templates/blog.liquid\` - Blog listing page - \`templates/article.liquid\` - Blog article page - \`templates/page.liquid\` - Pages created in the admin - \`templates/search.liquid\` - Search results page - \`templates/customers/\` - Customer account pages ### Sections The \`sections/\` directory contains modular components: - \`sections/header.liquid\` - Site header - \`sections/footer.liquid\` - Site footer - \`sections/product-template.liquid\` - Product details ### Snippets The \`snippets/\` directory contains reusable code snippets: - \`snippets/product-card.liquid\` - Product grid item ### Assets The \`assets/\` directory contains static files: - \`assets/theme.js\` - JavaScript - \`assets/theme.scss.liquid\` - Main stylesheet - Images, fonts, and other static files ### Config The \`config/\` directory contains: - \`config/settings_schema.json\` - Theme settings schema - \`config/settings_data.json\` - Theme settings data ### Layout The \`layout/\` directory contains: - \`layout/theme.liquid\` - Main layout template ### Locales The \`locales/\` directory contains translation files: - \`locales/en.default.json\` - English translations` }] }) ); return server; } // Connect to transport and start the server async function startServer() { try { console.log('Starting ecomTools MCP server...'); const server = await createServer(); const transport = new StdioServerTransport(); console.log('Connecting to transport...'); await server.connect(transport); console.log('Server connected and ready'); // Keep the process alive process.on('SIGINT', () => { console.log('Received SIGINT signal'); // Don't exit }); process.on('SIGTERM', () => { console.log('Received SIGTERM signal'); // Don't exit }); } catch (err) { console.error('Error starting server:', err); process.exit(1); } } module.exports = { createServer, startServer };