├── .gitignore ├── README.md ├── mcp ├── README.md └── woo │ ├── .env.example │ ├── .gitignore │ ├── README.md │ ├── TODO │ ├── package.json │ ├── src │ ├── server.ts │ ├── tools │ │ ├── coupons.ts │ │ ├── customers.ts │ │ ├── index.ts │ │ ├── orders.ts │ │ ├── payment_gateways.ts │ │ ├── product_variations.ts │ │ ├── products.ts │ │ ├── refunds.ts │ │ ├── shipping.ts │ │ └── tax_rates.ts │ ├── types │ │ └── woocommerce-types.ts │ └── woocommerce.ts │ └── tsconfig.json └── plugins └── llms-txt ├── README.md ├── includes ├── class-llms-txt-admin.php ├── class-llms-txt-content.php └── class-llms-txt-plugin.php └── llms-txt-plugin.php /.gitignore: -------------------------------------------------------------------------------- 1 | # Environment variables 2 | .env 3 | .env.production 4 | .env.testing 5 | .env.development 6 | .env.gcloud.yaml 7 | .env.testing 8 | 9 | # Logs 10 | logs 11 | *.log 12 | npm-debug.log* 13 | 14 | # Runtime data 15 | pids 16 | *.pid 17 | *.seed 18 | 19 | # IDE specific 20 | .idea 21 | .idea/* 22 | **/.idea/* 23 | **/.idea 24 | **/build/ 25 | 26 | # Python specific 27 | *.pyc 28 | **/__pycache__ 29 | 30 | # Node specific 31 | node_modules 32 | npm-debug.log* 33 | 34 | # IDE 35 | .idea/ 36 | .vscode/ 37 | *.swp 38 | *.swo 39 | **.DS_Store** -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Automattic AI Experiments 2 | 3 | Welcome to Automattic's **AI Experiments** monorepo—a public repository where we share prototypes, demos, and proof-of-concept projects that explore how AI can enhance the WordPress and WooCommerce ecosystems. 4 | 5 | ## Overview 6 | 7 | This repository serves as a public showcase where Automattic developers share experimental AI projects with the broader WordPress community. It's a space for open collaboration, learning, and gathering feedback. Projects here are experimental by nature — *there's no expectation of production-level stability or ongoing maintenance*. 8 | 9 | ## What You'll Find 10 | 11 | - **Model Context Protocol (MCP) Servers**: Located in the `/mcp` directory, these servers enable AI assistants to interact with Automattic services through natural language: 12 | - **WooCommerce MCP Server**: A full-featured server for AI-powered WooCommerce store management 13 | - **WordPress.com MCP Server**: (In Development) For interacting with WordPress.com services 14 | - **AI-Enabled Plugins**: WordPress plugins that leverage AI services or machine learning libraries 15 | - **LLMS TXT**: A plugin that exposes site content through structured text endpoints (`/llms.txt`) for LLM consumption 16 | - **Integration Prototypes**: Early ideations around AI features related to WooCommerce, WordPress.com, or other Automattic platforms 17 | 18 | Each subfolder contains its own experiment with a brief README explaining what it does, why it exists, and how to try it out. 19 | 20 | ## Contributing 21 | 22 | This repository is maintained by Automattic developers, but we welcome engagement from the entire WordPress community! Whether you want to try out experiments, provide feedback, or suggest improvements, here's how you can participate: 23 | 24 | 1. **Fork and Clone** this repo to explore and experiment with the code. 25 | 2. **Open Issues** to report bugs, suggest improvements, or start discussions. 26 | 3. **Submit Pull Requests** if you have ideas for enhancing existing experiments. 27 | 4. **Share Feedback** on what you'd like to see or how these experiments could be more useful. 28 | 29 | Automattic developers can also directly contribute new experiments following internal guidelines. 30 | 31 | ## License 32 | 33 | Unless noted otherwise, each project within this monorepo is open-source under the terms outlined in its respective folder. See the [`LICENSE`](./LICENSE) file or project-specific licenses for details. 34 | 35 | ## Questions or Feedback? 36 | 37 | - For general discussions, open an issue or post in the relevant experiment's folder. 38 | - If you have questions about a particular project, look for a README or contact details in that directory. 39 | 40 | Happy experimenting! 41 | -------------------------------------------------------------------------------- /mcp/README.md: -------------------------------------------------------------------------------- 1 | # Model Context Protocol (MCP) Packages 2 | 3 | This directory contains Model Context Protocol (MCP) server implementations that enable AI assistants to interact with various Automattic services through natural language. 4 | 5 | ## What is MCP? 6 | 7 | The Model Context Protocol (MCP) is a standardized way for AI language models to interact with external tools and services. It allows AI assistants to perform specific actions and access data through well-defined interfaces, making it possible to extend their capabilities in a structured way. 8 | 9 | ## Available Packages 10 | 11 | ### WooCommerce MCP Server (`/woo`) 12 | A full-featured MCP server that enables AI assistants to interact with WooCommerce stores. It provides tools for managing: 13 | - Products and variations 14 | - Orders and refunds 15 | - Customers 16 | - Coupons 17 | - Payment gateways 18 | - Tax rates 19 | - Shipping settings 20 | 21 | See the [WooCommerce MCP Server README](./woo/README.md) for detailed documentation. 22 | 23 | ### WordPress.com MCP Server (`/dotcom`) 24 | *(In Development)* 25 | A planned MCP server for interacting with WordPress.com services and functionality. 26 | 27 | ## Contributing 28 | 29 | Each MCP package in this directory follows these principles: 30 | 1. Clear documentation of available tools and their capabilities 31 | 2. Secure handling of authentication and sensitive data 32 | 3. Comprehensive error handling and input validation 33 | 4. Easy setup and configuration process 34 | 35 | If you're interested in contributing to existing packages or creating new ones, please refer to the main repository's contributing guidelines. -------------------------------------------------------------------------------- /mcp/woo/.env.example: -------------------------------------------------------------------------------- 1 | WOOCOMMERCE_API_URL=https://your-woocommerce-store.com # Include https:// 2 | WOOCOMMERCE_CONSUMER_KEY=ck_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 3 | WOOCOMMERCE_CONSUMER_SECRET=cs_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 4 | # WOOCOMMERCE_INSECURE_HTTP=true # ONLY for local dev with http, NEVER in production -------------------------------------------------------------------------------- /mcp/woo/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | .env 4 | pnpm-lock.yaml 5 | .DS_Store -------------------------------------------------------------------------------- /mcp/woo/README.md: -------------------------------------------------------------------------------- 1 | # WooCommerce MCP Server 2 | 3 | This is a Model Context Protocol (MCP) server for WooCommerce, allowing you to interact with your WooCommerce store using natural language via an MCP-compatible client like Claude for Desktop. This server exposes various WooCommerce data and functionality as MCP tools, making it accessible to LLMs. 4 | 5 | ## Features 6 | 7 | This server currently provides basic tools to interact with core WooCommerce data: 8 | 9 | * **Coupons:** 10 | * `list_coupons`: List all coupons (supports pagination and searching). 11 | * `get_coupon`: Retrieve a specific coupon by ID. 12 | * `create_coupon`: Create a new coupon. 13 | * `update_coupon`: Update an existing coupon. 14 | * `delete_coupon`: Delete a coupon. 15 | * **Customers:** 16 | * `list_customers`: List all customers (supports pagination, filtering and searching). 17 | * `get_customer`: Retrieve a specific customer by ID. 18 | * `create_customer`: Create a new customer. 19 | * `update_customer`: Update an existing customer. 20 | * `delete_customer`: Delete a customer. 21 | * **Orders:** 22 | * `list_orders`: List all orders (supports pagination, filtering and searching). 23 | * `get_order`: Retrieve a specific order by ID. 24 | * `create_order`: Create a new order. 25 | * `update_order`: Update an existing order. 26 | * `delete_order`: Delete an order. 27 | * **Products:** 28 | * `list_products`: List all products (supports pagination and searching). 29 | * `get_product`: Retrieve a specific product by ID. 30 | * `create_product`: Create a new product. 31 | * `update_product`: Update an existing product. 32 | * `delete_product`: Delete a product. 33 | * **Product Variations:** 34 | * `list_product_variations`: List all variations for a product. 35 | * `get_product_variation`: Retrieve a specific product variation. 36 | * `create_product_variation`: Create a new product variation. 37 | * `update_product_variation`: Update an existing product variation. 38 | * `delete_product_variation`: Delete a product variation. 39 | * **Tax Rates:** 40 | * `list_tax_rates`: List all tax rates (supports pagination and sorting). 41 | * `get_tax_rate`: Retrieve a specific tax rate by ID. 42 | * `create_tax_rate`: Create a new tax rate. 43 | * `update_tax_rate`: Update an existing tax rate. 44 | * `delete_tax_rate`: Delete a tax rate. 45 | * **Payment Gateways:** 46 | * `list_payment_gateways`: List all payment gateways. 47 | * `get_payment_gateway`: Retrieve a specific payment gateway. 48 | * `update_payment_gateway`: Update an existing payment gateway. 49 | * **Refunds:** 50 | * `list_refunds`: List all refunds for an order. 51 | * `get_refund`: Retrieve a specific refund. 52 | * `create_refund`: Create a new refund. 53 | * `delete_refund`: Delete a refund. 54 | 55 | More features and endpoints will be added in future updates. 56 | 57 | ## Prerequisites 58 | 59 | * **Node.js and npm:** Ensure you have Node.js (version 16 or higher) and npm installed. 60 | * **WooCommerce Store:** You need an active WooCommerce store with the REST API enabled. 61 | * **WooCommerce API Keys:** Generate REST API keys (Consumer Key and Consumer Secret) within your WooCommerce settings (WooCommerce > Settings > Advanced > REST API). These keys need **Read** access for the resources this server exposes. 62 | * **MCP Client:** You need an application that can communicate with the MCP Server. Currently, Claude Desktop is recommended. 63 | 64 | ## Installation and Setup 65 | 66 | 1. **Clone the Repository (or create a new project):** 67 | 68 | ```bash 69 | git clone # Replace with the actual repository URL 70 | cd woocommerce-mcp-server 71 | ``` 72 | 73 | Or, if creating a new project from scratch: 74 | 75 | ```bash 76 | mkdir woocommerce-mcp-server 77 | cd woocommerce-mcp-server 78 | npm init -y 79 | ``` 80 | 81 | 2. **Install Dependencies:** 82 | 83 | ```bash 84 | npm install 85 | ``` 86 | or, if you are creating a new project 87 | ```bash 88 | npm install @modelcontextprotocol/sdk @woocommerce/woocommerce-rest-api dotenv zod 89 | npm install -D typescript @types/node ts-node 90 | ``` 91 | If you cloned, you will need to also install `ts-node` by doing 92 | ``` 93 | npm install -D ts-node 94 | ``` 95 | 96 | 3. **Create a `.env` file:** 97 | 98 | Create a `.env` file in the root of your project directory and add your WooCommerce API credentials: 99 | 100 | ```env 101 | WOOCOMMERCE_API_URL=https://your-woocommerce-store.com # Your store's URL, MUST include https:// 102 | WOOCOMMERCE_CONSUMER_KEY=ck_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 103 | WOOCOMMERCE_CONSUMER_SECRET=cs_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 104 | ``` 105 | 106 | **Replace the placeholders with your actual values.** The `WOOCOMMERCE_API_URL` *must* include the `https://` protocol. 107 | 108 | 4. **Build the Server (if cloned or using TypeScript):** 109 | 110 | ```bash 111 | npm run build 112 | ``` 113 | This compiles the TypeScript code to JavaScript. 114 | 115 | 5. **Configure Claude Desktop:** 116 | 117 | * Open Claude Desktop settings and navigate to the "Developer" tab. 118 | * Click "Edit Config" to open the `claude_desktop_config.json` file. 119 | * Add a new server configuration under the `mcpServers` section. You will need to provide the **absolute** path to the `build/server.js` file and your WooCommerce environment variables: 120 | 121 | ```json 122 | { 123 | "mcpServers": { 124 | "woocommerce": { 125 | "command": "node", 126 | "args": ["/absolute/path/to/woocommerce-mcp-server/build/server.js"], 127 | "env": { 128 | "WOOCOMMERCE_API_URL": "https://your-woocommerce-store.com", 129 | "WOOCOMMERCE_CONSUMER_KEY": "ck_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 130 | "WOOCOMMERCE_CONSUMER_SECRET": "cs_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" 131 | } 132 | } 133 | } 134 | } 135 | ``` 136 | 137 | **Important:** 138 | * Replace `/absolute/path/to/woocommerce-mcp-server/build/server.js` with the actual *absolute* path to your `server.js` file. 139 | * Replace the environment variables with your actual WooCommerce API credentials. 140 | * The `WOOCOMMERCE_API_URL` *must* include the `https://` protocol. 141 | 142 | * Save the `claude_desktop_config.json` file and **restart Claude Desktop**. 143 | 144 | Note: While we previously mentioned creating a `.env` file, when using Claude Desktop, the environment variables should be specified directly in the `claude_desktop_config.json` file as shown above. The `.env` file is only needed if you're running the server directly from the command line. 145 | 146 | ## Running the Server 147 | 148 | *If using Claude for Desktop:* 149 | 150 | Once you've configured `claude_desktop_config.json` and restarted Claude for Desktop, the server should start automatically whenever Claude for Desktop starts. 151 | 152 | *If using the command line directly:* 153 | 154 | You can also run the server directly from the command line for testing (this won't connect to Claude for Desktop): 155 | 156 | ```bash 157 | npm start 158 | ``` 159 | 160 | or if you did not build, 161 | ```bash 162 | npm run dev 163 | ``` 164 | 165 | You'll see a message indicating the server is running: "WooCommerce MCP Server running on stdio". You can then use a tool like the [MCP Inspector](https://github.com/modelcontextprotocol/inspector) to interact with it. 166 | 167 | ## Using the Server with Claude for Desktop 168 | 169 | After restarting Claude for Desktop with the server configured, you should see a hammer icon in the bottom right corner of the input box. Clicking this icon will show you the available tools provided by the server. 170 | 171 | You can now interact with your WooCommerce store through natural language. For example, you can try: 172 | 173 | * "List all my WooCommerce coupons" 174 | * "Get the customer with ID 123" 175 | * "Show me all orders from the last month" (You might need to expand the server's capabilities to handle date filtering.) 176 | * "List products with 'shirt' in the name" 177 | * "What's the price of product 42?" 178 | 179 | Claude will use the available MCP tools to fulfill your requests. It will ask for your permission before executing any tool calls. 180 | 181 | ## Troubleshooting 182 | 183 | * **Server not showing up in Claude Desktop:** 184 | * Double-check the `command` and `args` in your `claude_desktop_config.json` file. The path to `server.js` must be **absolute**, not relative. 185 | * Make sure you have restarted Claude Desktop after making changes to the configuration file. 186 | * Check the Claude Desktop logs for errors (see the [Debugging Guide](/docs/tools/debugging) for log locations). Look for errors related to starting the server process. 187 | * Try running the server directly from the command line (`npm start`) to see if it starts up without errors. 188 | * Ensure Node.js is installed and accessible in your PATH. 189 | * Ensure you have run `npm install` to install dependencies. 190 | * **"Tool not found" error:** 191 | * Verify that the tool name you're using matches the `name` defined in the server code. Tool names are case-sensitive. 192 | * **WooCommerce API errors:** 193 | * Double-check your WooCommerce API URL, consumer key, and consumer secret in the `.env` file. 194 | * Ensure the API keys have the necessary read permissions for the resources you're trying to access. 195 | * Check the WooCommerce logs for more detailed error messages. 196 | * If using HTTPS, ensure your WooCommerce store has a valid SSL certificate. For *local development only*, you can use `WOOCOMMERCE_INSECURE_HTTP=true` to bypass certificate checks, but **do not use this in production.** 197 | * **"Cannot find package" errors:** 198 | * Ensure you installed the correct packages. Run `npm install` again to be sure. 199 | * **Permissions errors:** 200 | * Ensure you are running as a user that has access to the filepaths on the server and the configured WooCommerce instance. 201 | * **Other errors:** 202 | * Check the server's console output (if running from the command line) for any error messages. 203 | * Use a debugger to step through the server code and identify the issue. 204 | * Refer to the [MCP Debugging Guide](/docs/tools/debugging) for more advanced debugging techniques. 205 | * Make sure your WooCommerce URL includes the `https://` at the beginning. 206 | * Ensure your MCP server does not run on port 80 or 443. 207 | 208 | ## Extending the Server 209 | 210 | This basic server is built for flexibility and can be extended by modifying two key files: server.ts and woocommerce.ts. You can enhance its capabilities in several ways: 211 | 212 | * **Adding More Tools:** Define new tool specifications and implement their handlers. For WooCommerce-specific endpoints, add the necessary integrations in woocommerce.ts, then register the new tools in server.ts. 213 | * **Implementing More Complex Logic:** Update the tool handlers in server.ts to incorporate advanced processing and data formatting as needed. 214 | * **Adding Resources:** Extend server.ts by implementing additional endpoints (such as resources/list and resources/read) to expose data directly. 215 | * **Adding Prompts:** Create or refine prompt templates that guide the LLM in utilizing the full range of updated tools and functionalities. 216 | 217 | ## Security 218 | 219 | * **Never commit your API keys or secrets to version control.** Use environment variables (the `.env` file) to store them securely. 220 | * **Limit API key permissions:** Only grant the necessary permissions (read-only, if possible) to the WooCommerce API keys you use with the MCP server. 221 | * **Validate Inputs:** Thoroughly validate all inputs received from the client (especially tool arguments) to prevent injection attacks or unintended behavior. 222 | * **Consider HTTPS:** Use HTTPS for communication between the client and server, *especially* if you're not using the stdio transport. The quickstart enables `queryStringAuth` and disables certificate checking for local development only. For production, use proper HTTPS certificates. 223 | * **Rate Limiting:** Implement rate limiting to prevent abuse of your server and the WooCommerce API. 224 | 225 | ## Contributing 226 | 227 | Contributions are welcome! Please submit a pull request with your changes. Ensure your code follows the existing style and includes tests where appropriate. 228 | -------------------------------------------------------------------------------- /mcp/woo/TODO: -------------------------------------------------------------------------------- 1 | [] Automattically remove tools (read/write) based on API key permissions 2 | [] Handle destructive tools (delete, update) more gracefully -------------------------------------------------------------------------------- /mcp/woo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "woocommerce-mcp-server", 3 | "version": "0.0.1", 4 | "description": "A Model Context Protocol server for interacting with WooCommerce.", 5 | "type": "module", 6 | "main": "./build/server.js", 7 | "exports": "./build/server.js", 8 | "engines": { 9 | "node": ">=18.0.0" 10 | }, 11 | "scripts": { 12 | "build": "tsc --project tsconfig.json", 13 | "start": "node ./build/server.js", 14 | "dev": "tsx watch src/server.ts", 15 | "clean": "rimraf build" 16 | }, 17 | "keywords": [ 18 | "woocommerce", 19 | "mcp", 20 | "server", 21 | "claude", 22 | "ai" 23 | ], 24 | "author": "James LePage", 25 | "license": "GPL-3.0-or-later", 26 | "dependencies": { 27 | "@modelcontextprotocol/sdk": "^1.4.1", 28 | "axios": "^1.6.7", 29 | "dotenv": "^16.4.5", 30 | "zod": "^3.23.8", 31 | "zod-to-json-schema": "^3.24.1" 32 | }, 33 | "devDependencies": { 34 | "@types/node": "^22.10.0", 35 | "rimraf": "^5.0.5", 36 | "tsx": "^4.7.1", 37 | "typescript": "^5.3.3" 38 | } 39 | } -------------------------------------------------------------------------------- /mcp/woo/src/server.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // src/server.ts 3 | import * as dotenv from 'dotenv'; 4 | dotenv.config(); // Load environment variables from .env first 5 | 6 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; 7 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; 8 | import { allTools, toolHandlers } from './tools/index.js'; 9 | import { z } from 'zod'; 10 | 11 | // Create MCP server instance 12 | const server = new McpServer({ 13 | name: "woocommerce", // Simplified name 14 | version: "0.0.1" 15 | }, { 16 | capabilities: { 17 | tools: allTools.reduce((acc, tool) => { 18 | acc[tool.name] = tool; 19 | return acc; 20 | }, {} as Record) 21 | } 22 | }); 23 | 24 | // Register each tool from our tools list with its corresponding handler 25 | for (const tool of allTools) { 26 | const handler = toolHandlers[tool.name as keyof typeof toolHandlers]; 27 | if (!handler) continue; 28 | 29 | const wrappedHandler = async (args: any) => { 30 | // The handler functions are already typed with their specific parameter types 31 | const result = await handler(args); 32 | return { 33 | content: result.toolResult.content.map((item: { type: string; text: string }) => ({ 34 | ...item, 35 | type: "text" as const 36 | })), 37 | isError: result.toolResult.isError 38 | }; 39 | }; 40 | 41 | // Create a schema that allows any properties - validation is handled by the tool handlers 42 | const schema = z.object({}).catchall(z.unknown()); 43 | server.tool(tool.name, schema.shape, wrappedHandler); 44 | } 45 | 46 | async function main() { 47 | // console.log('Starting WooCommerce MCP server...'); 48 | 49 | if (!process.env.WOOCOMMERCE_API_URL || !process.env.WOOCOMMERCE_CONSUMER_KEY || !process.env.WOOCOMMERCE_CONSUMER_SECRET) { 50 | // console.error('Missing required environment variables. Please check your .env file.'); 51 | process.exit(1); 52 | } 53 | 54 | if (process.env.WOOCOMMERCE_API_URL?.startsWith('http:') && process.env.WOOCOMMERCE_INSECURE_HTTP !== 'true') { 55 | // console.error('Insecure HTTP URL detected. Set WOOCOMMERCE_INSECURE_HTTP=true to allow HTTP connections.'); 56 | process.exit(1); 57 | } 58 | 59 | try { 60 | // console.log('Initializing WooCommerce client...'); 61 | const { initWooCommerce } = await import('./woocommerce.js'); 62 | await initWooCommerce(); 63 | // console.log('WooCommerce client initialized successfully.'); 64 | 65 | // console.log('Setting up server transport...'); 66 | const transport = new StdioServerTransport(); 67 | await server.connect(transport); 68 | // console.log('Server transport connected successfully.'); 69 | } catch (error) { 70 | // console.error('Failed to initialize server:', error); 71 | process.exit(1); 72 | } 73 | } 74 | 75 | // Handle process signals and errors silently 76 | process.on('SIGTERM', () => { 77 | // console.log('Received SIGTERM signal, shutting down...'); 78 | process.exit(0); 79 | }); 80 | process.on('SIGINT', () => { 81 | // console.log('Received SIGINT signal, shutting down...'); 82 | process.exit(0); 83 | }); 84 | process.on('uncaughtException', (error) => { 85 | // console.error('Uncaught exception:', error); 86 | process.exit(1); 87 | }); 88 | process.on('unhandledRejection', (error) => { 89 | // console.error('Unhandled rejection:', error); 90 | process.exit(1); 91 | }); 92 | 93 | main().catch(() => process.exit(1)); -------------------------------------------------------------------------------- /mcp/woo/src/tools/coupons.ts: -------------------------------------------------------------------------------- 1 | // src/tools/coupons.ts 2 | import { z } from 'zod'; 3 | import { zodToJsonSchema } from 'zod-to-json-schema'; 4 | import { Tool } from '@modelcontextprotocol/sdk/types.js'; 5 | import { makeWooCommerceRequest } from '../woocommerce.js'; 6 | import { WooCommerceCoupon } from '../types/woocommerce-types.js'; 7 | 8 | const listCouponsSchema = z.object({ 9 | page: z.number().optional().describe("Page number (default: 1)"), 10 | per_page: z.number().optional().describe("Items per page (default: 10, max: 100)"), 11 | search: z.string().optional().describe("Search term for coupon code or description"), 12 | after: z.string().optional().describe("Limit response to coupons published after a given ISO8601 date"), 13 | before: z.string().optional().describe("Limit response to coupons published before a given ISO8601 date"), 14 | exclude: z.array(z.number()).optional().describe("Exclude specific coupon IDs from the result"), 15 | include: z.array(z.number()).optional().describe("Limit result set to specific coupon IDs"), 16 | offset: z.number().optional().describe("Offset the result set by a specific number of items"), 17 | order: z.enum(['asc', 'desc']).optional().describe("Order sort attribute ascending or descending"), 18 | orderby: z.enum(['date', 'id', 'include', 'title', 'slug']).optional().describe("Sort coupons by parameter") 19 | }); 20 | 21 | const createCouponSchema = z.object({ 22 | code: z.string().describe("Coupon code"), 23 | discount_type: z.enum(['percent', 'fixed_cart', 'fixed_product']).describe("Determines the type of discount that will be applied"), 24 | amount: z.string().describe("The amount of discount"), 25 | description: z.string().optional().describe("Coupon description"), 26 | date_expires: z.string().optional().describe("The date the coupon expires, in the site's timezone"), 27 | individual_use: z.boolean().optional().describe("If true, the coupon can only be used individually"), 28 | product_ids: z.array(z.number()).optional().describe("List of product IDs the coupon can be used on"), 29 | excluded_product_ids: z.array(z.number()).optional().describe("List of product IDs the coupon cannot be used on"), 30 | usage_limit: z.number().optional().describe("How many times the coupon can be used in total"), 31 | usage_limit_per_user: z.number().optional().describe("How many times the coupon can be used per customer"), 32 | limit_usage_to_x_items: z.number().optional().describe("Max number of items in the cart the coupon can be applied to"), 33 | free_shipping: z.boolean().optional().describe("If true and if the free shipping method requires a coupon, this coupon will enable free shipping"), 34 | product_categories: z.array(z.number()).optional().describe("List of category IDs the coupon applies to"), 35 | excluded_product_categories: z.array(z.number()).optional().describe("List of category IDs the coupon does not apply to"), 36 | exclude_sale_items: z.boolean().optional().describe("If true, this coupon will not be applied to items that have sale prices"), 37 | minimum_amount: z.string().optional().describe("Minimum order amount that needs to be in the cart before coupon applies"), 38 | maximum_amount: z.string().optional().describe("Maximum order amount allowed when using the coupon"), 39 | email_restrictions: z.array(z.string()).optional().describe("List of email addresses that can use this coupon"), 40 | meta_data: z.array(z.object({ 41 | key: z.string(), 42 | value: z.string() 43 | })).optional().describe("Meta data") 44 | }); 45 | 46 | const getCouponSchema = z.object({ 47 | id: z.number().describe("The coupon ID") 48 | }); 49 | 50 | const updateCouponSchema = createCouponSchema.extend({ 51 | id: z.number().describe("Coupon ID") 52 | }).partial(); 53 | 54 | const deleteCouponSchema = z.object({ 55 | id: z.number().describe("The coupon ID"), 56 | force: z.boolean().optional().describe("Required to be true, as resource does not support trashing") 57 | }); 58 | 59 | type ListCouponsParams = z.infer; 60 | type CreateCouponParams = z.infer; 61 | type GetCouponParams = z.infer; 62 | type UpdateCouponParams = z.infer; 63 | type DeleteCouponParams = z.infer; 64 | 65 | export const couponTools: Tool[] = [ 66 | { 67 | name: "list_coupons", 68 | description: "Lists all coupons with filtering, sorting, and pagination options", 69 | inputSchema: zodToJsonSchema(listCouponsSchema) as { type: "object"; properties: Record } 70 | }, 71 | { 72 | name: "create_coupon", 73 | description: "Creates a new coupon", 74 | inputSchema: zodToJsonSchema(createCouponSchema) as { type: "object"; properties: Record } 75 | }, 76 | { 77 | name: "get_coupon", 78 | description: "Gets a coupon by ID", 79 | inputSchema: zodToJsonSchema(getCouponSchema) as { type: "object"; properties: Record } 80 | }, 81 | { 82 | name: "update_coupon", 83 | description: "Updates an existing coupon", 84 | inputSchema: zodToJsonSchema(updateCouponSchema) as { type: "object"; properties: Record } 85 | }, 86 | { 87 | name: "delete_coupon", 88 | description: "Deletes a coupon", 89 | inputSchema: zodToJsonSchema(deleteCouponSchema) as { type: "object"; properties: Record } 90 | } 91 | ]; 92 | 93 | export const couponHandlers = { 94 | list_coupons: async (params: ListCouponsParams) => { 95 | try { 96 | const response = await makeWooCommerceRequest('GET', "coupons", params); 97 | const coupons: WooCommerceCoupon[] = response; 98 | return { 99 | toolResult: { 100 | content: [{ type: 'text', text: JSON.stringify(coupons, null, 2) }], 101 | }, 102 | }; 103 | } catch (error: any) { 104 | const errorMessage = error.response?.data?.message || error.message; 105 | return { 106 | toolResult: { 107 | isError: true, 108 | content: [{ type: 'text', text: `Error listing coupons: ${errorMessage}` }], 109 | }, 110 | }; 111 | } 112 | }, 113 | create_coupon: async (params: CreateCouponParams) => { 114 | try { 115 | const response = await makeWooCommerceRequest('POST', "coupons", params); 116 | const coupon: WooCommerceCoupon = response; 117 | return { 118 | toolResult: { 119 | content: [{ type: 'text', text: JSON.stringify(coupon, null, 2) }], 120 | }, 121 | }; 122 | } catch (error: any) { 123 | const errorMessage = error.response?.data?.message || error.message; 124 | return { 125 | toolResult: { 126 | isError: true, 127 | content: [{ type: 'text', text: `Error creating coupon: ${errorMessage}` }], 128 | }, 129 | }; 130 | } 131 | }, 132 | get_coupon: async (params: GetCouponParams) => { 133 | try { 134 | const response = await makeWooCommerceRequest('GET', `coupons/${params.id}`); 135 | const coupon: WooCommerceCoupon = response; 136 | return { 137 | toolResult: { 138 | content: [{ type: 'text', text: JSON.stringify(coupon, null, 2) }], 139 | }, 140 | }; 141 | } catch (error: any) { 142 | const errorMessage = error.response?.data?.message || error.message; 143 | return { 144 | toolResult: { 145 | isError: true, 146 | content: [{ type: 'text', text: `Error getting coupon: ${errorMessage}` }], 147 | }, 148 | }; 149 | } 150 | }, 151 | update_coupon: async (params: UpdateCouponParams) => { 152 | try { 153 | const { id, ...updateData } = params; 154 | const response = await makeWooCommerceRequest('PUT', `coupons/${id}`, updateData); 155 | const coupon: WooCommerceCoupon = response; 156 | return { 157 | toolResult: { 158 | content: [{ type: 'text', text: JSON.stringify(coupon, null, 2) }], 159 | }, 160 | }; 161 | } catch (error: any) { 162 | const errorMessage = error.response?.data?.message || error.message; 163 | return { 164 | toolResult: { 165 | isError: true, 166 | content: [{ type: 'text', text: `Error updating coupon: ${errorMessage}` }], 167 | }, 168 | }; 169 | } 170 | }, 171 | delete_coupon: async (params: DeleteCouponParams) => { 172 | try { 173 | const response = await makeWooCommerceRequest('DELETE', `coupons/${params.id}`, { force: params.force }); 174 | const coupon: WooCommerceCoupon = response; 175 | return { 176 | toolResult: { 177 | content: [{ type: 'text', text: JSON.stringify(coupon, null, 2) }], 178 | }, 179 | }; 180 | } catch (error: any) { 181 | const errorMessage = error.response?.data?.message || error.message; 182 | return { 183 | toolResult: { 184 | isError: true, 185 | content: [{ type: 'text', text: `Error deleting coupon: ${errorMessage}` }], 186 | }, 187 | }; 188 | } 189 | } 190 | }; -------------------------------------------------------------------------------- /mcp/woo/src/tools/customers.ts: -------------------------------------------------------------------------------- 1 | // src/tools/customers.ts 2 | import { Tool } from '@modelcontextprotocol/sdk/types.js'; 3 | import { makeWooCommerceRequest } from '../woocommerce.js'; 4 | import { WooCommerceCustomer } from '../types/woocommerce-types.js'; 5 | import { z } from 'zod'; 6 | import { zodToJsonSchema } from 'zod-to-json-schema'; 7 | 8 | const listCustomersSchema = z.object({ 9 | page: z.number().optional().describe("Page number (default 1)"), 10 | per_page: z.number().min(1).max(100).optional().describe("Items per page (default 10, max 100)"), 11 | search: z.string().optional().describe("Search term for customer email or name"), 12 | exclude: z.array(z.number()).optional().describe("Exclude specific customer IDs from the result"), 13 | include: z.array(z.number()).optional().describe("Limit result set to specific customer IDs"), 14 | offset: z.number().optional().describe("Offset the result set by a specific number of items"), 15 | orderby: z.enum(['id', 'include', 'name', 'email', 'date_created', 'date_modified']).optional() 16 | .describe("Sort customers by parameter"), 17 | order: z.enum(['asc', 'desc']).optional().describe("Order sort attribute ascending or descending"), 18 | role: z.enum(['all', 'administrator', 'customer', 'subscriber']).optional() 19 | .describe("Filter customers by role"), 20 | email: z.string().email().optional().describe("Limit result set to customers matching email") 21 | }); 22 | 23 | const billingSchema = z.object({ 24 | first_name: z.string().optional(), 25 | last_name: z.string().optional(), 26 | company: z.string().optional(), 27 | address_1: z.string().optional(), 28 | address_2: z.string().optional(), 29 | city: z.string().optional(), 30 | state: z.string().optional(), 31 | postcode: z.string().optional(), 32 | country: z.string().optional(), 33 | email: z.string().email().optional(), 34 | phone: z.string().optional() 35 | }).optional(); 36 | 37 | const shippingSchema = z.object({ 38 | first_name: z.string().optional(), 39 | last_name: z.string().optional(), 40 | company: z.string().optional(), 41 | address_1: z.string().optional(), 42 | address_2: z.string().optional(), 43 | city: z.string().optional(), 44 | state: z.string().optional(), 45 | postcode: z.string().optional(), 46 | country: z.string().optional() 47 | }).optional(); 48 | 49 | const metaDataSchema = z.array(z.object({ 50 | key: z.string(), 51 | value: z.string() 52 | })).optional(); 53 | 54 | const createCustomerSchema = z.object({ 55 | email: z.string().email().describe("Customer email address"), 56 | first_name: z.string().optional().describe("Customer first name"), 57 | last_name: z.string().optional().describe("Customer last name"), 58 | username: z.string().optional().describe("Customer username"), 59 | password: z.string().optional().describe("Customer password"), 60 | billing: billingSchema.describe("Customer billing address"), 61 | shipping: shippingSchema.describe("Customer shipping address"), 62 | meta_data: metaDataSchema.describe("Meta data") 63 | }).strict(); 64 | 65 | const getCustomerSchema = z.object({ 66 | id: z.number().describe("Customer ID") 67 | }).strict(); 68 | 69 | const updateCustomerSchema = z.object({ 70 | id: z.number().describe("Customer ID"), 71 | email: z.string().email().optional().describe("Customer email address"), 72 | first_name: z.string().optional().describe("Customer first name"), 73 | last_name: z.string().optional().describe("Customer last name"), 74 | username: z.string().optional().describe("Customer username"), 75 | password: z.string().optional().describe("Customer password"), 76 | billing: billingSchema.describe("Customer billing address"), 77 | shipping: shippingSchema.describe("Customer shipping address"), 78 | meta_data: metaDataSchema.describe("Meta data") 79 | }).strict(); 80 | 81 | const deleteCustomerSchema = z.object({ 82 | id: z.number().describe("Customer ID"), 83 | force: z.boolean().optional().describe("Required to be true, as resource does not support trashing"), 84 | reassign: z.number().optional().describe("User ID to reassign posts to") 85 | }).strict(); 86 | 87 | type ListCustomersParams = z.infer; 88 | type CreateCustomerParams = z.infer; 89 | type GetCustomerParams = z.infer; 90 | type UpdateCustomerParams = z.infer; 91 | type DeleteCustomerParams = z.infer; 92 | 93 | export const customerTools: Tool[] = [ 94 | { 95 | name: "list_customers", 96 | description: "Lists all customers with filtering, sorting, and pagination options", 97 | inputSchema: zodToJsonSchema(listCustomersSchema) as { type: "object"; properties: Record } 98 | }, 99 | { 100 | name: "create_customer", 101 | description: "Creates a new customer", 102 | inputSchema: zodToJsonSchema(createCustomerSchema) as { type: "object"; properties: Record } 103 | }, 104 | { 105 | name: "get_customer", 106 | description: "Gets a customer by ID", 107 | inputSchema: zodToJsonSchema(getCustomerSchema) as { type: "object"; properties: Record } 108 | }, 109 | { 110 | name: "update_customer", 111 | description: "Updates an existing customer", 112 | inputSchema: zodToJsonSchema(updateCustomerSchema) as { type: "object"; properties: Record } 113 | }, 114 | { 115 | name: "delete_customer", 116 | description: "Deletes a customer", 117 | inputSchema: zodToJsonSchema(deleteCustomerSchema) as { type: "object"; properties: Record } 118 | } 119 | ]; 120 | 121 | export const customerHandlers = { 122 | list_customers: async (params: ListCustomersParams) => { 123 | try { 124 | const response = await makeWooCommerceRequest('GET', "customers", params); 125 | const customers: WooCommerceCustomer[] = response; 126 | return { 127 | toolResult: { 128 | content: [{ type: 'text', text: JSON.stringify(customers, null, 2) }], 129 | }, 130 | }; 131 | } catch (error: any) { 132 | const errorMessage = error.response?.data?.message || error.message; 133 | return { 134 | toolResult: { 135 | isError: true, 136 | content: [{ type: 'text', text: `Error listing customers: ${errorMessage}` }], 137 | }, 138 | }; 139 | } 140 | }, 141 | create_customer: async (params: CreateCustomerParams) => { 142 | try { 143 | const response = await makeWooCommerceRequest('POST', "customers", params); 144 | const customer: WooCommerceCustomer = response; 145 | return { 146 | toolResult: { 147 | content: [{ type: 'text', text: JSON.stringify(customer, null, 2) }], 148 | }, 149 | }; 150 | } catch (error: any) { 151 | const errorMessage = error.response?.data?.message || error.message; 152 | return { 153 | toolResult: { 154 | isError: true, 155 | content: [{ type: 'text', text: `Error creating customer: ${errorMessage}` }], 156 | }, 157 | }; 158 | } 159 | }, 160 | get_customer: async (params: GetCustomerParams) => { 161 | try { 162 | const response = await makeWooCommerceRequest('GET', `customers/${params.id}`); 163 | const customer: WooCommerceCustomer = response; 164 | return { 165 | toolResult: { 166 | content: [{ type: 'text', text: JSON.stringify(customer, null, 2) }], 167 | }, 168 | }; 169 | } catch (error: any) { 170 | const errorMessage = error.response?.data?.message || error.message; 171 | return { 172 | toolResult: { 173 | isError: true, 174 | content: [{ type: 'text', text: `Error getting customer: ${errorMessage}` }], 175 | }, 176 | }; 177 | } 178 | }, 179 | update_customer: async (params: UpdateCustomerParams) => { 180 | try { 181 | const { id, ...updateData } = params; 182 | const response = await makeWooCommerceRequest('PUT', `customers/${id}`, updateData); 183 | const customer: WooCommerceCustomer = response; 184 | return { 185 | toolResult: { 186 | content: [{ type: 'text', text: JSON.stringify(customer, null, 2) }], 187 | }, 188 | }; 189 | } catch (error: any) { 190 | const errorMessage = error.response?.data?.message || error.message; 191 | return { 192 | toolResult: { 193 | isError: true, 194 | content: [{ type: 'text', text: `Error updating customer: ${errorMessage}` }], 195 | }, 196 | }; 197 | } 198 | }, 199 | delete_customer: async (params: DeleteCustomerParams) => { 200 | try { 201 | const response = await makeWooCommerceRequest('DELETE', `customers/${params.id}`, { force: params.force, reassign: params.reassign }); 202 | const customer: WooCommerceCustomer = response; 203 | return { 204 | toolResult: { 205 | content: [{ type: 'text', text: JSON.stringify(customer, null, 2) }], 206 | }, 207 | }; 208 | } catch (error: any) { 209 | const errorMessage = error.response?.data?.message || error.message; 210 | return { 211 | toolResult: { 212 | isError: true, 213 | content: [{ type: 'text', text: `Error deleting customer: ${errorMessage}` }], 214 | }, 215 | }; 216 | } 217 | } 218 | }; -------------------------------------------------------------------------------- /mcp/woo/src/tools/index.ts: -------------------------------------------------------------------------------- 1 | // src/tools/index.ts 2 | import { customerTools, customerHandlers } from './customers.js'; 3 | import { orderTools, orderHandlers } from './orders.js'; 4 | import { productTools, productHandlers } from './products.js'; 5 | import { couponTools, couponHandlers } from './coupons.js'; 6 | import { taxRateTools, taxRateHandlers } from './tax_rates.js'; 7 | import { shippingTools, shippingHandlers } from './shipping.js'; 8 | import { paymentGatewayTools, paymentGatewayHandlers } from './payment_gateways.js'; 9 | import { refundTools, refundHandlers } from './refunds.js'; 10 | import { productVariationTools, productVariationHandlers } from './product_variations.js'; 11 | import { Tool } from '@modelcontextprotocol/sdk/types.js'; 12 | 13 | // Aggregate all tools for easy import 14 | export const allTools = [ 15 | ...customerTools, 16 | ...orderTools, 17 | ...productTools, 18 | ...couponTools, 19 | ...taxRateTools, 20 | ...shippingTools, 21 | ...paymentGatewayTools, 22 | ...refundTools, 23 | ...productVariationTools 24 | ]; 25 | 26 | // Export the handler mapping with an index signature 27 | export const toolHandlers = { 28 | ...customerHandlers, 29 | ...orderHandlers, 30 | ...productHandlers, 31 | ...couponHandlers, 32 | ...taxRateHandlers, 33 | ...shippingHandlers, 34 | ...paymentGatewayHandlers, 35 | ...refundHandlers, 36 | ...productVariationHandlers 37 | }; -------------------------------------------------------------------------------- /mcp/woo/src/tools/orders.ts: -------------------------------------------------------------------------------- 1 | // src/tools/orders.ts 2 | import { Tool } from '@modelcontextprotocol/sdk/types.js'; 3 | import { makeWooCommerceRequest } from '../woocommerce.js'; 4 | import { WooCommerceOrder } from '../types/woocommerce-types.js'; 5 | import { z } from 'zod'; 6 | import { zodToJsonSchema } from 'zod-to-json-schema'; 7 | 8 | const listOrdersSchema = z.object({ 9 | page: z.number().optional().describe("Page number (default 1)"), 10 | per_page: z.number().min(1).max(100).optional().describe("Items per page (default 10, max 100)"), 11 | search: z.string().optional().describe("Search term for order ID or customer"), 12 | after: z.string().optional().describe("Limit response to orders placed after a given ISO8601 date"), 13 | before: z.string().optional().describe("Limit response to orders placed before a given ISO8601 date"), 14 | exclude: z.array(z.number()).optional().describe("Exclude specific order IDs from the result"), 15 | include: z.array(z.number()).optional().describe("Limit result set to specific order IDs"), 16 | offset: z.number().optional().describe("Offset the result set by a specific number of items"), 17 | customer: z.number().optional().describe("Limit result set to orders assigned to specific customer IDs"), 18 | status: z.enum(['any', 'pending', 'processing', 'on-hold', 'completed', 'cancelled', 'refunded', 'failed']) 19 | .optional().describe("Limit result set to orders assigned a specific status"), 20 | orderby: z.enum(['date', 'id', 'include', 'title', 'slug']).optional() 21 | .describe("Sort orders by parameter"), 22 | order: z.enum(['asc', 'desc']).optional().describe("Order sort attribute ascending or descending"), 23 | customer_is_paying: z.boolean().optional().describe("Filter orders by customers who are paying or have paid"), 24 | product: z.number().optional().describe("Filter orders by product ID"), 25 | dp: z.number().optional().describe("Number of decimal points to use in each resource") 26 | }); 27 | 28 | const createOrderSchema = z.object({ 29 | payment_method: z.string().optional().describe("Payment method ID"), 30 | payment_method_title: z.string().optional().describe("Payment method title"), 31 | set_paid: z.boolean().optional().describe("Define if the order is paid. It will set the status to processing and reduce stock items"), 32 | billing: z.object({ 33 | first_name: z.string().optional(), 34 | last_name: z.string().optional(), 35 | company: z.string().optional(), 36 | address_1: z.string().optional(), 37 | address_2: z.string().optional(), 38 | city: z.string().optional(), 39 | state: z.string().optional(), 40 | postcode: z.string().optional(), 41 | country: z.string().optional(), 42 | email: z.string().email().optional(), 43 | phone: z.string().optional() 44 | }).optional().describe("Billing address"), 45 | shipping: z.object({ 46 | first_name: z.string().optional(), 47 | last_name: z.string().optional(), 48 | company: z.string().optional(), 49 | address_1: z.string().optional(), 50 | address_2: z.string().optional(), 51 | city: z.string().optional(), 52 | state: z.string().optional(), 53 | postcode: z.string().optional(), 54 | country: z.string().optional() 55 | }).optional().describe("Shipping address"), 56 | line_items: z.array(z.object({ 57 | product_id: z.number().describe("Product ID"), 58 | quantity: z.number().describe("Quantity"), 59 | variation_id: z.number().optional().describe("Variation ID, if applicable"), 60 | meta_data: z.array(z.object({ 61 | key: z.string(), 62 | value: z.string() 63 | })).optional().describe("Meta data") 64 | })).optional().describe("Line items data"), 65 | shipping_lines: z.array(z.object({ 66 | method_id: z.string().describe("Shipping method ID"), 67 | method_title: z.string().describe("Shipping method title"), 68 | total: z.string().describe("Shipping total") 69 | })).optional().describe("Shipping lines data"), 70 | coupon_lines: z.array(z.object({ 71 | code: z.string().describe("Coupon code") 72 | })).optional().describe("Coupon lines data"), 73 | customer_id: z.number().optional().describe("User ID who owns the order. 0 for guests"), 74 | customer_note: z.string().optional().describe("Note left by customer during checkout"), 75 | meta_data: z.array(z.object({ 76 | key: z.string(), 77 | value: z.string() 78 | })).optional().describe("Meta data") 79 | }); 80 | 81 | const getOrderSchema = z.object({ 82 | id: z.number().describe("Order ID") 83 | }); 84 | 85 | const updateOrderSchema = createOrderSchema.extend({ 86 | id: z.number().describe("Order ID"), 87 | status: z.enum(['pending', 'processing', 'on-hold', 'completed', 'cancelled', 'refunded', 'failed']).optional() 88 | .describe("Order status") 89 | }).partial(); 90 | 91 | const deleteOrderSchema = z.object({ 92 | id: z.number().describe("Order ID"), 93 | force: z.boolean().optional().describe("Required to be true, as resource does not support trashing") 94 | }); 95 | 96 | type ListOrdersParams = z.infer; 97 | type CreateOrderParams = z.infer; 98 | type GetOrderParams = z.infer; 99 | type UpdateOrderParams = z.infer; 100 | type DeleteOrderParams = z.infer; 101 | 102 | export const orderTools: Tool[] = [ 103 | { 104 | name: "list_orders", 105 | description: "Lists all orders with filtering, sorting, and pagination options", 106 | inputSchema: zodToJsonSchema(listOrdersSchema) as { type: "object"; properties: Record } 107 | }, 108 | { 109 | name: "create_order", 110 | description: "Creates a new order", 111 | inputSchema: zodToJsonSchema(createOrderSchema) as { type: "object"; properties: Record } 112 | }, 113 | { 114 | name: "get_order", 115 | description: "Gets an order by ID", 116 | inputSchema: zodToJsonSchema(getOrderSchema) as { type: "object"; properties: Record } 117 | }, 118 | { 119 | name: "update_order", 120 | description: "Updates an existing order", 121 | inputSchema: zodToJsonSchema(updateOrderSchema) as { type: "object"; properties: Record } 122 | }, 123 | { 124 | name: "delete_order", 125 | description: "Deletes an order", 126 | inputSchema: zodToJsonSchema(deleteOrderSchema) as { type: "object"; properties: Record } 127 | } 128 | ]; 129 | 130 | export const orderHandlers = { 131 | list_orders: async (params: ListOrdersParams) => { 132 | try { 133 | const response = await makeWooCommerceRequest('GET', "orders", params); 134 | const orders: WooCommerceOrder[] = response; 135 | return { 136 | toolResult: { 137 | content: [{ type: 'text', text: JSON.stringify(orders, null, 2) }], 138 | }, 139 | }; 140 | } catch (error: any) { 141 | const errorMessage = error.response?.data?.message || error.message; 142 | return { 143 | toolResult: { 144 | isError: true, 145 | content: [{ type: 'text', text: `Error listing orders: ${errorMessage}` }], 146 | }, 147 | }; 148 | } 149 | }, 150 | create_order: async (params: CreateOrderParams) => { 151 | try { 152 | const response = await makeWooCommerceRequest('POST', "orders", params); 153 | const order: WooCommerceOrder = response; 154 | return { 155 | toolResult: { 156 | content: [{ type: 'text', text: JSON.stringify(order, null, 2) }], 157 | }, 158 | }; 159 | } catch (error: any) { 160 | const errorMessage = error.response?.data?.message || error.message; 161 | return { 162 | toolResult: { 163 | isError: true, 164 | content: [{ type: 'text', text: `Error creating order: ${errorMessage}` }], 165 | }, 166 | }; 167 | } 168 | }, 169 | get_order: async (params: GetOrderParams) => { 170 | try { 171 | const response = await makeWooCommerceRequest('GET', `orders/${params.id}`); 172 | const order: WooCommerceOrder = response; 173 | return { 174 | toolResult: { 175 | content: [{ type: 'text', text: JSON.stringify(order, null, 2) }], 176 | }, 177 | }; 178 | } catch (error: any) { 179 | const errorMessage = error.response?.data?.message || error.message; 180 | return { 181 | toolResult: { 182 | isError: true, 183 | content: [{ type: 'text', text: `Error getting order: ${errorMessage}` }], 184 | }, 185 | }; 186 | } 187 | }, 188 | update_order: async (params: UpdateOrderParams) => { 189 | try { 190 | const { id, ...updateData } = params; 191 | const response = await makeWooCommerceRequest('PUT', `orders/${id}`, updateData); 192 | const order: WooCommerceOrder = response; 193 | return { 194 | toolResult: { 195 | content: [{ type: 'text', text: JSON.stringify(order, null, 2) }], 196 | }, 197 | }; 198 | } catch (error: any) { 199 | const errorMessage = error.response?.data?.message || error.message; 200 | return { 201 | toolResult: { 202 | isError: true, 203 | content: [{ type: 'text', text: `Error updating order: ${errorMessage}` }], 204 | }, 205 | }; 206 | } 207 | }, 208 | delete_order: async (params: DeleteOrderParams) => { 209 | try { 210 | const response = await makeWooCommerceRequest('DELETE', `orders/${params.id}`, { force: params.force }); 211 | const order: WooCommerceOrder = response; 212 | return { 213 | toolResult: { 214 | content: [{ type: 'text', text: JSON.stringify(order, null, 2) }], 215 | }, 216 | }; 217 | } catch (error: any) { 218 | const errorMessage = error.response?.data?.message || error.message; 219 | return { 220 | toolResult: { 221 | isError: true, 222 | content: [{ type: 'text', text: `Error deleting order: ${errorMessage}` }], 223 | }, 224 | }; 225 | } 226 | } 227 | }; -------------------------------------------------------------------------------- /mcp/woo/src/tools/payment_gateways.ts: -------------------------------------------------------------------------------- 1 | import { Tool } from '@modelcontextprotocol/sdk/types.js'; 2 | import { makeWooCommerceRequest } from '../woocommerce.js'; 3 | import { z } from 'zod'; 4 | import { zodToJsonSchema } from 'zod-to-json-schema'; 5 | import { WooCommercePaymentGateway } from '../types/woocommerce-types.js'; 6 | 7 | const listPaymentGatewaysSchema = z.object({}); 8 | 9 | const getPaymentGatewaySchema = z.object({ 10 | id: z.string().describe("Payment gateway ID") 11 | }); 12 | 13 | const updatePaymentGatewaySchema = z.object({ 14 | id: z.string().describe("Payment gateway ID"), 15 | title: z.string().optional().describe("Payment gateway title"), 16 | description: z.string().optional().describe("Payment gateway description"), 17 | order: z.number().optional().describe("Payment gateway order"), 18 | enabled: z.boolean().optional().describe("Whether the gateway is enabled"), 19 | settings: z.record(z.any()).optional().describe("Payment gateway settings") 20 | }); 21 | 22 | type ListPaymentGatewaysParams = z.infer; 23 | type GetPaymentGatewayParams = z.infer; 24 | type UpdatePaymentGatewayParams = z.infer; 25 | 26 | export const paymentGatewayTools: Tool[] = [ 27 | { 28 | name: "list_payment_gateways", 29 | description: "Lists all payment gateways", 30 | inputSchema: zodToJsonSchema(listPaymentGatewaysSchema) as { type: "object"; properties: Record } 31 | }, 32 | { 33 | name: "get_payment_gateway", 34 | description: "Gets a payment gateway by ID", 35 | inputSchema: zodToJsonSchema(getPaymentGatewaySchema) as { type: "object"; properties: Record } 36 | }, 37 | { 38 | name: "update_payment_gateway", 39 | description: "Updates an existing payment gateway", 40 | inputSchema: zodToJsonSchema(updatePaymentGatewaySchema) as { type: "object"; properties: Record } 41 | } 42 | ]; 43 | 44 | export const paymentGatewayHandlers = { 45 | list_payment_gateways: async () => { 46 | try { 47 | const response = await makeWooCommerceRequest('GET', "payment_gateways"); 48 | const paymentGateways: WooCommercePaymentGateway[] = response; 49 | return { 50 | toolResult: { 51 | content: [{ type: 'text', text: JSON.stringify(paymentGateways, null, 2) }], 52 | }, 53 | }; 54 | } catch (error: any) { 55 | const errorMessage = error.response?.data?.message || error.message; 56 | return { 57 | toolResult: { 58 | isError: true, 59 | content: [{ type: 'text', text: `Error listing payment gateways: ${errorMessage}` }], 60 | }, 61 | }; 62 | } 63 | }, 64 | get_payment_gateway: async (params: GetPaymentGatewayParams) => { 65 | try { 66 | const response = await makeWooCommerceRequest('GET', `payment_gateways/${params.id}`); 67 | const paymentGateway: WooCommercePaymentGateway = response; 68 | return { 69 | toolResult: { 70 | content: [{ type: 'text', text: JSON.stringify(paymentGateway, null, 2) }], 71 | }, 72 | }; 73 | } catch (error: any) { 74 | const errorMessage = error.response?.data?.message || error.message; 75 | return { 76 | toolResult: { 77 | isError: true, 78 | content: [{ type: 'text', text: `Error getting payment gateway: ${errorMessage}` }], 79 | }, 80 | }; 81 | } 82 | }, 83 | update_payment_gateway: async (params: UpdatePaymentGatewayParams) => { 84 | try { 85 | const { id, ...updateData } = params; 86 | const response = await makeWooCommerceRequest('PUT', `payment_gateways/${id}`, updateData); 87 | const paymentGateway: WooCommercePaymentGateway = response; 88 | return { 89 | toolResult: { 90 | content: [{ type: 'text', text: JSON.stringify(paymentGateway, null, 2) }], 91 | }, 92 | }; 93 | } catch (error: any) { 94 | const errorMessage = error.response?.data?.message || error.message; 95 | return { 96 | toolResult: { 97 | isError: true, 98 | content: [{ type: 'text', text: `Error updating payment gateway: ${errorMessage}` }], 99 | }, 100 | }; 101 | } 102 | } 103 | }; -------------------------------------------------------------------------------- /mcp/woo/src/tools/product_variations.ts: -------------------------------------------------------------------------------- 1 | import { Tool } from '@modelcontextprotocol/sdk/types.js'; 2 | import { makeWooCommerceRequest } from '../woocommerce.js'; 3 | import { z } from 'zod'; 4 | import { zodToJsonSchema } from 'zod-to-json-schema'; 5 | import { WooCommerceProductVariation } from '../types/woocommerce-types.js'; 6 | 7 | const listProductVariationsSchema = z.object({ 8 | product_id: z.number().describe("Product ID to list variations for"), 9 | page: z.number().optional().describe("Page number (default 1)"), 10 | per_page: z.number().min(1).max(100).optional().describe("Items per page (default 10, max 100)"), 11 | search: z.string().optional().describe("Search term for variation SKU"), 12 | after: z.string().optional().describe("Limit response to variations published after a given ISO8601 date"), 13 | before: z.string().optional().describe("Limit response to variations published before a given ISO8601 date"), 14 | exclude: z.array(z.number()).optional().describe("Exclude specific variation IDs from the result"), 15 | include: z.array(z.number()).optional().describe("Limit result set to specific variation IDs"), 16 | offset: z.number().optional().describe("Offset the result set by a specific number of items"), 17 | order: z.enum(['asc', 'desc']).optional().describe("Order sort attribute ascending or descending"), 18 | orderby: z.enum(['date', 'id', 'include', 'title', 'slug']).optional().describe("Sort variations by parameter"), 19 | parent: z.array(z.number()).optional().describe("Limit result set to variations with specific parent IDs"), 20 | parent_exclude: z.array(z.number()).optional().describe("Limit result set to all items except variations with specific parent IDs"), 21 | slug: z.string().optional().describe("Limit result set to variations with a specific slug"), 22 | status: z.enum(['draft', 'pending', 'private', 'publish']).optional().describe("Limit result set to variations with a specific status"), 23 | sku: z.string().optional().describe("Limit result set to variations with a specific SKU"), 24 | tax_class: z.string().optional().describe("Limit result set to variations with a specific tax class"), 25 | on_sale: z.boolean().optional().describe("Limit result set to variations on sale"), 26 | min_price: z.string().optional().describe("Limit result set to variations based on a minimum price"), 27 | max_price: z.string().optional().describe("Limit result set to variations based on a maximum price"), 28 | stock_status: z.enum(['instock', 'outofstock', 'onbackorder']).optional().describe("Limit result set to variations with specified stock status") 29 | }); 30 | 31 | const createProductVariationSchema = z.object({ 32 | product_id: z.number().describe("Product ID to create variation for"), 33 | description: z.string().optional().describe("Variation description"), 34 | sku: z.string().optional().describe("Unique identifier"), 35 | regular_price: z.string().optional().describe("Variation regular price"), 36 | sale_price: z.string().optional().describe("Variation sale price"), 37 | status: z.enum(['draft', 'pending', 'private', 'publish']).optional().describe("Variation status"), 38 | virtual: z.boolean().optional().describe("If the variation is virtual"), 39 | downloadable: z.boolean().optional().describe("If the variation is downloadable"), 40 | downloads: z.array(z.object({ 41 | id: z.string().optional(), 42 | name: z.string(), 43 | file: z.string() 44 | })).optional().describe("List of downloadable files"), 45 | download_limit: z.number().optional().describe("Number of times downloadable files can be downloaded after purchase"), 46 | download_expiry: z.number().optional().describe("Number of days until download link expires"), 47 | tax_status: z.enum(['taxable', 'shipping', 'none']).optional().describe("Tax status"), 48 | tax_class: z.string().optional().describe("Tax class"), 49 | manage_stock: z.boolean().optional().describe("Stock management at variation level"), 50 | stock_quantity: z.number().optional().describe("Stock quantity"), 51 | stock_status: z.enum(['instock', 'outofstock', 'onbackorder']).optional().describe("Controls the stock status of the variation"), 52 | backorders: z.enum(['no', 'notify', 'yes']).optional().describe("If managing stock, this controls if backorders are allowed"), 53 | weight: z.string().optional().describe("Variation weight"), 54 | dimensions: z.object({ 55 | length: z.string(), 56 | width: z.string(), 57 | height: z.string() 58 | }).optional().describe("Variation dimensions"), 59 | shipping_class: z.string().optional().describe("Shipping class slug"), 60 | image: z.object({ 61 | id: z.number().optional(), 62 | src: z.string().optional(), 63 | name: z.string().optional(), 64 | alt: z.string().optional() 65 | }).optional().describe("Variation image data"), 66 | attributes: z.array(z.object({ 67 | id: z.number().optional(), 68 | name: z.string(), 69 | option: z.string() 70 | })).optional().describe("List of attributes"), 71 | menu_order: z.number().optional().describe("Menu order, used to custom sort variations") 72 | }); 73 | 74 | const getProductVariationSchema = z.object({ 75 | product_id: z.number().describe("Product ID the variation belongs to"), 76 | id: z.number().describe("Variation ID") 77 | }); 78 | 79 | const updateProductVariationSchema = createProductVariationSchema.extend({ 80 | id: z.number().describe("Variation ID") 81 | }).partial(); 82 | 83 | const deleteProductVariationSchema = z.object({ 84 | product_id: z.number().describe("Product ID the variation belongs to"), 85 | id: z.number().describe("Variation ID"), 86 | force: z.boolean().optional().describe("Required to be true, as resource does not support trashing") 87 | }); 88 | 89 | type ListProductVariationsParams = z.infer; 90 | type CreateProductVariationParams = z.infer; 91 | type GetProductVariationParams = z.infer; 92 | type UpdateProductVariationParams = z.infer; 93 | type DeleteProductVariationParams = z.infer; 94 | 95 | export const productVariationTools: Tool[] = [ 96 | { 97 | name: "list_product_variations", 98 | description: "Lists all variations for a product", 99 | inputSchema: zodToJsonSchema(listProductVariationsSchema) as { type: "object"; properties: Record } 100 | }, 101 | { 102 | name: "create_product_variation", 103 | description: "Creates a new variation for a product", 104 | inputSchema: zodToJsonSchema(createProductVariationSchema) as { type: "object"; properties: Record } 105 | }, 106 | { 107 | name: "get_product_variation", 108 | description: "Gets a product variation by ID", 109 | inputSchema: zodToJsonSchema(getProductVariationSchema) as { type: "object"; properties: Record } 110 | }, 111 | { 112 | name: "update_product_variation", 113 | description: "Updates an existing product variation", 114 | inputSchema: zodToJsonSchema(updateProductVariationSchema) as { type: "object"; properties: Record } 115 | }, 116 | { 117 | name: "delete_product_variation", 118 | description: "Deletes a product variation", 119 | inputSchema: zodToJsonSchema(deleteProductVariationSchema) as { type: "object"; properties: Record } 120 | } 121 | ]; 122 | 123 | export const productVariationHandlers = { 124 | list_product_variations: async (params: ListProductVariationsParams) => { 125 | try { 126 | const { product_id, ...queryParams } = params; 127 | const response = await makeWooCommerceRequest('GET', `products/${product_id}/variations`, queryParams); 128 | const variations: WooCommerceProductVariation[] = response; 129 | return { 130 | toolResult: { 131 | content: [{ type: 'text', text: JSON.stringify(variations, null, 2) }], 132 | }, 133 | }; 134 | } catch (error: any) { 135 | const errorMessage = error.response?.data?.message || error.message; 136 | return { 137 | toolResult: { 138 | isError: true, 139 | content: [{ type: 'text', text: `Error listing product variations: ${errorMessage}` }], 140 | }, 141 | }; 142 | } 143 | }, 144 | create_product_variation: async (params: CreateProductVariationParams) => { 145 | try { 146 | const { product_id, ...variationData } = params; 147 | const response = await makeWooCommerceRequest('POST', `products/${product_id}/variations`, variationData); 148 | const variation: WooCommerceProductVariation = response; 149 | return { 150 | toolResult: { 151 | content: [{ type: 'text', text: JSON.stringify(variation, null, 2) }], 152 | }, 153 | }; 154 | } catch (error: any) { 155 | const errorMessage = error.response?.data?.message || error.message; 156 | return { 157 | toolResult: { 158 | isError: true, 159 | content: [{ type: 'text', text: `Error creating product variation: ${errorMessage}` }], 160 | }, 161 | }; 162 | } 163 | }, 164 | get_product_variation: async (params: GetProductVariationParams) => { 165 | try { 166 | const response = await makeWooCommerceRequest('GET', `products/${params.product_id}/variations/${params.id}`); 167 | const variation: WooCommerceProductVariation = response; 168 | return { 169 | toolResult: { 170 | content: [{ type: 'text', text: JSON.stringify(variation, null, 2) }], 171 | }, 172 | }; 173 | } catch (error: any) { 174 | const errorMessage = error.response?.data?.message || error.message; 175 | return { 176 | toolResult: { 177 | isError: true, 178 | content: [{ type: 'text', text: `Error getting product variation: ${errorMessage}` }], 179 | }, 180 | }; 181 | } 182 | }, 183 | update_product_variation: async (params: UpdateProductVariationParams) => { 184 | try { 185 | const { product_id, id, ...updateData } = params; 186 | const response = await makeWooCommerceRequest('PUT', `products/${product_id}/variations/${id}`, updateData); 187 | const variation: WooCommerceProductVariation = response; 188 | return { 189 | toolResult: { 190 | content: [{ type: 'text', text: JSON.stringify(variation, null, 2) }], 191 | }, 192 | }; 193 | } catch (error: any) { 194 | const errorMessage = error.response?.data?.message || error.message; 195 | return { 196 | toolResult: { 197 | isError: true, 198 | content: [{ type: 'text', text: `Error updating product variation: ${errorMessage}` }], 199 | }, 200 | }; 201 | } 202 | }, 203 | delete_product_variation: async (params: DeleteProductVariationParams) => { 204 | try { 205 | const response = await makeWooCommerceRequest('DELETE', `products/${params.product_id}/variations/${params.id}`, { force: params.force }); 206 | const variation: WooCommerceProductVariation = response; 207 | return { 208 | toolResult: { 209 | content: [{ type: 'text', text: JSON.stringify(variation, null, 2) }], 210 | }, 211 | }; 212 | } catch (error: any) { 213 | const errorMessage = error.response?.data?.message || error.message; 214 | return { 215 | toolResult: { 216 | isError: true, 217 | content: [{ type: 'text', text: `Error deleting product variation: ${errorMessage}` }], 218 | }, 219 | }; 220 | } 221 | } 222 | }; -------------------------------------------------------------------------------- /mcp/woo/src/tools/products.ts: -------------------------------------------------------------------------------- 1 | // src/tools/products.ts 2 | import { Tool } from '@modelcontextprotocol/sdk/types.js'; 3 | import { makeWooCommerceRequest } from '../woocommerce.js'; 4 | import { WooCommerceProduct } from '../types/woocommerce-types.js'; 5 | import { z } from 'zod'; 6 | import { zodToJsonSchema } from 'zod-to-json-schema'; 7 | 8 | const listProductsSchema = z.object({ 9 | page: z.number().optional().describe("Page number (default 1)"), 10 | per_page: z.number().min(1).max(100).optional().describe("Items per page (default 10, max 100)"), 11 | search: z.string().optional().describe("Search term for product name or SKU"), 12 | after: z.string().optional().describe("Limit response to products published after a given ISO8601 date"), 13 | before: z.string().optional().describe("Limit response to products published before a given ISO8601 date"), 14 | exclude: z.array(z.number()).optional().describe("Exclude specific product IDs from the result"), 15 | include: z.array(z.number()).optional().describe("Limit result set to specific product IDs"), 16 | offset: z.number().optional().describe("Offset the result set by a specific number of items"), 17 | featured: z.boolean().optional().describe("Limit result set to featured products"), 18 | category: z.string().optional().describe("Limit result set to products assigned a specific category ID"), 19 | tag: z.string().optional().describe("Limit result set to products assigned a specific tag ID"), 20 | status: z.enum(['draft', 'pending', 'private', 'publish']).optional() 21 | .describe("Limit result set to products assigned a specific status"), 22 | type: z.enum(['simple', 'grouped', 'external', 'variable']).optional() 23 | .describe("Limit result set to products assigned a specific type"), 24 | sku: z.string().optional().describe("Limit result set to products with a specific SKU"), 25 | orderby: z.enum(['date', 'id', 'include', 'title', 'slug', 'price', 'popularity', 'rating']) 26 | .optional().describe("Sort products by parameter"), 27 | order: z.enum(['asc', 'desc']).optional().describe("Order sort attribute ascending or descending"), 28 | on_sale: z.boolean().optional().describe("Limit result set to products on sale"), 29 | min_price: z.string().optional().describe("Limit result set to products based on a minimum price"), 30 | max_price: z.string().optional().describe("Limit result set to products based on a maximum price"), 31 | stock_status: z.enum(['instock', 'outofstock', 'onbackorder']).optional().describe("Limit result set to products with specified stock status") 32 | }); 33 | 34 | const createProductSchema = z.object({ 35 | name: z.string().describe("Product name"), 36 | type: z.enum(['simple', 'grouped', 'external', 'variable']).optional().describe("Product type"), 37 | status: z.enum(['draft', 'pending', 'private', 'publish']).optional().describe("Product status"), 38 | featured: z.boolean().optional().describe("Featured product"), 39 | catalog_visibility: z.enum(['visible', 'catalog', 'search', 'hidden']).optional().describe("Catalog visibility"), 40 | description: z.string().optional().describe("Product description"), 41 | short_description: z.string().optional().describe("Product short description"), 42 | sku: z.string().optional().describe("Unique identifier"), 43 | regular_price: z.string().optional().describe("Product regular price"), 44 | sale_price: z.string().optional().describe("Product sale price"), 45 | date_on_sale_from: z.string().optional().describe("Start date of sale price, in the site's timezone"), 46 | date_on_sale_to: z.string().optional().describe("End date of sale price, in the site's timezone"), 47 | virtual: z.boolean().optional().describe("If the product is virtual"), 48 | downloadable: z.boolean().optional().describe("If the product is downloadable"), 49 | downloads: z.array(z.object({ 50 | id: z.string().optional(), 51 | name: z.string(), 52 | file: z.string() 53 | })).optional().describe("List of downloadable files"), 54 | download_limit: z.number().optional().describe("Number of times downloadable files can be downloaded after purchase"), 55 | download_expiry: z.number().optional().describe("Number of days until download link expires"), 56 | external_url: z.string().optional().describe("Product external URL. Only for external products"), 57 | button_text: z.string().optional().describe("Product external button text. Only for external products"), 58 | tax_status: z.enum(['taxable', 'shipping', 'none']).optional().describe("Tax status"), 59 | tax_class: z.string().optional().describe("Tax class"), 60 | manage_stock: z.boolean().optional().describe("Stock management at product level"), 61 | stock_quantity: z.number().optional().describe("Stock quantity"), 62 | stock_status: z.enum(['instock', 'outofstock', 'onbackorder']).optional().describe("Controls the stock status of the product"), 63 | backorders: z.enum(['no', 'notify', 'yes']).optional().describe("If managing stock, this controls if backorders are allowed"), 64 | sold_individually: z.boolean().optional().describe("Allow one item to be bought in a single order"), 65 | weight: z.string().optional().describe("Product weight"), 66 | dimensions: z.object({ 67 | length: z.string(), 68 | width: z.string(), 69 | height: z.string() 70 | }).optional().describe("Product dimensions"), 71 | shipping_class: z.string().optional().describe("Shipping class slug"), 72 | reviews_allowed: z.boolean().optional().describe("Allow reviews"), 73 | upsell_ids: z.array(z.number()).optional().describe("List of up-sell products IDs"), 74 | cross_sell_ids: z.array(z.number()).optional().describe("List of cross-sell products IDs"), 75 | parent_id: z.number().optional().describe("Product parent ID"), 76 | purchase_note: z.string().optional().describe("Optional note to send the customer after purchase"), 77 | categories: z.array(z.object({ 78 | id: z.number().optional(), 79 | name: z.string().optional(), 80 | slug: z.string().optional() 81 | })).optional().describe("List of categories"), 82 | tags: z.array(z.object({ 83 | id: z.number().optional(), 84 | name: z.string().optional(), 85 | slug: z.string().optional() 86 | })).optional().describe("List of tags"), 87 | attributes: z.array(z.object({ 88 | id: z.number().optional(), 89 | name: z.string(), 90 | position: z.number().optional(), 91 | visible: z.boolean().optional(), 92 | variation: z.boolean().optional(), 93 | options: z.array(z.string()) 94 | })).optional().describe("List of attributes"), 95 | default_attributes: z.array(z.object({ 96 | id: z.number().optional(), 97 | name: z.string(), 98 | option: z.string() 99 | })).optional().describe("Defaults variation attributes"), 100 | grouped_products: z.array(z.number()).optional().describe("List of grouped products ID"), 101 | menu_order: z.number().optional().describe("Menu order, used to custom sort products"), 102 | meta_data: z.array(z.object({ 103 | key: z.string(), 104 | value: z.string() 105 | })).optional().describe("Meta data") 106 | }); 107 | 108 | const getProductSchema = z.object({ 109 | id: z.number().describe("Product ID") 110 | }); 111 | 112 | const updateProductSchema = createProductSchema.extend({ 113 | id: z.number().describe("Product ID") 114 | }).partial(); 115 | 116 | const deleteProductSchema = z.object({ 117 | id: z.number().describe("Product ID"), 118 | force: z.boolean().optional().describe("Required to be true, as resource does not support trashing") 119 | }); 120 | 121 | type ListProductsParams = z.infer; 122 | type CreateProductParams = z.infer; 123 | type GetProductParams = z.infer; 124 | type UpdateProductParams = z.infer; 125 | type DeleteProductParams = z.infer; 126 | 127 | export const productTools: Tool[] = [ 128 | { 129 | name: "list_products", 130 | description: "Lists all products with filtering, sorting, and pagination options", 131 | inputSchema: zodToJsonSchema(listProductsSchema) as { type: "object"; properties: Record } 132 | }, 133 | { 134 | name: "create_product", 135 | description: "Creates a new product", 136 | inputSchema: zodToJsonSchema(createProductSchema) as { type: "object"; properties: Record } 137 | }, 138 | { 139 | name: "get_product", 140 | description: "Gets a product by ID", 141 | inputSchema: zodToJsonSchema(getProductSchema) as { type: "object"; properties: Record } 142 | }, 143 | { 144 | name: "update_product", 145 | description: "Updates an existing product", 146 | inputSchema: zodToJsonSchema(updateProductSchema) as { type: "object"; properties: Record } 147 | }, 148 | { 149 | name: "delete_product", 150 | description: "Deletes a product", 151 | inputSchema: zodToJsonSchema(deleteProductSchema) as { type: "object"; properties: Record } 152 | } 153 | ]; 154 | 155 | export const productHandlers = { 156 | list_products: async (params: ListProductsParams) => { 157 | try { 158 | const response = await makeWooCommerceRequest('GET', "products", params); 159 | const products: WooCommerceProduct[] = response; 160 | return { 161 | toolResult: { 162 | content: [{ type: 'text', text: JSON.stringify(products, null, 2) }], 163 | }, 164 | }; 165 | } catch (error: any) { 166 | const errorMessage = error.response?.data?.message || error.message; 167 | return { 168 | toolResult: { 169 | isError: true, 170 | content: [{ type: 'text', text: `Error listing products: ${errorMessage}` }], 171 | }, 172 | }; 173 | } 174 | }, 175 | create_product: async (params: CreateProductParams) => { 176 | try { 177 | const response = await makeWooCommerceRequest('POST', "products", params); 178 | const product: WooCommerceProduct = response; 179 | return { 180 | toolResult: { 181 | content: [{ type: 'text', text: JSON.stringify(product, null, 2) }], 182 | }, 183 | }; 184 | } catch (error: any) { 185 | const errorMessage = error.response?.data?.message || error.message; 186 | return { 187 | toolResult: { 188 | isError: true, 189 | content: [{ type: 'text', text: `Error creating product: ${errorMessage}` }], 190 | }, 191 | }; 192 | } 193 | }, 194 | get_product: async (params: GetProductParams) => { 195 | try { 196 | const response = await makeWooCommerceRequest('GET', `products/${params.id}`); 197 | const product: WooCommerceProduct = response; 198 | return { 199 | toolResult: { 200 | content: [{ type: 'text', text: JSON.stringify(product, null, 2) }], 201 | }, 202 | }; 203 | } catch (error: any) { 204 | const errorMessage = error.response?.data?.message || error.message; 205 | return { 206 | toolResult: { 207 | isError: true, 208 | content: [{ type: 'text', text: `Error getting product: ${errorMessage}` }], 209 | }, 210 | }; 211 | } 212 | }, 213 | update_product: async (params: UpdateProductParams) => { 214 | try { 215 | const { id, ...updateData } = params; 216 | const response = await makeWooCommerceRequest('PUT', `products/${id}`, updateData); 217 | const product: WooCommerceProduct = response; 218 | return { 219 | toolResult: { 220 | content: [{ type: 'text', text: JSON.stringify(product, null, 2) }], 221 | }, 222 | }; 223 | } catch (error: any) { 224 | const errorMessage = error.response?.data?.message || error.message; 225 | return { 226 | toolResult: { 227 | isError: true, 228 | content: [{ type: 'text', text: `Error updating product: ${errorMessage}` }], 229 | }, 230 | }; 231 | } 232 | }, 233 | delete_product: async (params: DeleteProductParams) => { 234 | try { 235 | const response = await makeWooCommerceRequest('DELETE', `products/${params.id}`, { force: params.force }); 236 | const product: WooCommerceProduct = response; 237 | return { 238 | toolResult: { 239 | content: [{ type: 'text', text: JSON.stringify(product, null, 2) }], 240 | }, 241 | }; 242 | } catch (error: any) { 243 | const errorMessage = error.response?.data?.message || error.message; 244 | return { 245 | toolResult: { 246 | isError: true, 247 | content: [{ type: 'text', text: `Error deleting product: ${errorMessage}` }], 248 | }, 249 | }; 250 | } 251 | } 252 | }; -------------------------------------------------------------------------------- /mcp/woo/src/tools/refunds.ts: -------------------------------------------------------------------------------- 1 | import { Tool } from '@modelcontextprotocol/sdk/types.js'; 2 | import { makeWooCommerceRequest } from '../woocommerce.js'; 3 | import { z } from 'zod'; 4 | import { zodToJsonSchema } from 'zod-to-json-schema'; 5 | import { WooCommerceRefund } from '../types/woocommerce-types.js'; 6 | 7 | const listRefundsSchema = z.object({ 8 | order_id: z.number().describe("Order ID to list refunds for"), 9 | page: z.number().optional().describe("Page number (default 1)"), 10 | per_page: z.number().min(1).max(100).optional().describe("Items per page (default 10, max 100)") 11 | }); 12 | 13 | const createRefundSchema = z.object({ 14 | order_id: z.number().describe("Order ID to create refund for"), 15 | amount: z.number().optional().describe("Refund amount"), 16 | reason: z.string().optional().describe("Reason for refund"), 17 | refunded_by: z.number().optional().describe("User ID of user who created the refund"), 18 | meta_data: z.array(z.object({ 19 | key: z.string(), 20 | value: z.string() 21 | })).optional().describe("Meta data"), 22 | line_items: z.array(z.object({ 23 | id: z.number().describe("Line item ID"), 24 | quantity: z.number().optional().describe("Quantity to refund"), 25 | refund_total: z.number().optional().describe("Total amount to refund for this line item"), 26 | refund_tax: z.array(z.object({ 27 | id: z.number(), 28 | amount: z.number() 29 | })).optional().describe("Tax amounts to refund") 30 | })).optional().describe("Line items to refund"), 31 | api_refund: z.boolean().optional().describe("When true, the payment gateway API is used to generate the refund") 32 | }); 33 | 34 | const getRefundSchema = z.object({ 35 | order_id: z.number().describe("Order ID the refund belongs to"), 36 | id: z.number().describe("Refund ID") 37 | }); 38 | 39 | const deleteRefundSchema = z.object({ 40 | order_id: z.number().describe("Order ID the refund belongs to"), 41 | id: z.number().describe("Refund ID"), 42 | force: z.boolean().optional().describe("Required to be true, as resource does not support trashing") 43 | }); 44 | 45 | type ListRefundsParams = z.infer; 46 | type CreateRefundParams = z.infer; 47 | type GetRefundParams = z.infer; 48 | type DeleteRefundParams = z.infer; 49 | 50 | export const refundTools: Tool[] = [ 51 | { 52 | name: "list_refunds", 53 | description: "Lists all refunds for an order", 54 | inputSchema: zodToJsonSchema(listRefundsSchema) as { type: "object"; properties: Record } 55 | }, 56 | { 57 | name: "create_refund", 58 | description: "Creates a new refund for an order", 59 | inputSchema: zodToJsonSchema(createRefundSchema) as { type: "object"; properties: Record } 60 | }, 61 | { 62 | name: "get_refund", 63 | description: "Gets a refund by ID", 64 | inputSchema: zodToJsonSchema(getRefundSchema) as { type: "object"; properties: Record } 65 | }, 66 | { 67 | name: "delete_refund", 68 | description: "Deletes a refund", 69 | inputSchema: zodToJsonSchema(deleteRefundSchema) as { type: "object"; properties: Record } 70 | } 71 | ]; 72 | 73 | export const refundHandlers = { 74 | list_refunds: async (params: ListRefundsParams) => { 75 | try { 76 | const response = await makeWooCommerceRequest('GET', `orders/${params.order_id}/refunds`, params); 77 | const refunds: WooCommerceRefund[] = response; 78 | return { 79 | toolResult: { 80 | content: [{ type: 'text', text: JSON.stringify(refunds, null, 2) }], 81 | }, 82 | }; 83 | } catch (error: any) { 84 | const errorMessage = error.response?.data?.message || error.message; 85 | return { 86 | toolResult: { 87 | isError: true, 88 | content: [{ type: 'text', text: `Error listing refunds: ${errorMessage}` }], 89 | }, 90 | }; 91 | } 92 | }, 93 | create_refund: async (params: CreateRefundParams) => { 94 | try { 95 | const { order_id, ...refundData } = params; 96 | const response = await makeWooCommerceRequest('POST', `orders/${order_id}/refunds`, refundData); 97 | const refund: WooCommerceRefund = response; 98 | return { 99 | toolResult: { 100 | content: [{ type: 'text', text: JSON.stringify(refund, null, 2) }], 101 | }, 102 | }; 103 | } catch (error: any) { 104 | const errorMessage = error.response?.data?.message || error.message; 105 | return { 106 | toolResult: { 107 | isError: true, 108 | content: [{ type: 'text', text: `Error creating refund: ${errorMessage}` }], 109 | }, 110 | }; 111 | } 112 | }, 113 | get_refund: async (params: GetRefundParams) => { 114 | try { 115 | const response = await makeWooCommerceRequest('GET', `orders/${params.order_id}/refunds/${params.id}`); 116 | const refund: WooCommerceRefund = response; 117 | return { 118 | toolResult: { 119 | content: [{ type: 'text', text: JSON.stringify(refund, null, 2) }], 120 | }, 121 | }; 122 | } catch (error: any) { 123 | const errorMessage = error.response?.data?.message || error.message; 124 | return { 125 | toolResult: { 126 | isError: true, 127 | content: [{ type: 'text', text: `Error getting refund: ${errorMessage}` }], 128 | }, 129 | }; 130 | } 131 | }, 132 | delete_refund: async (params: DeleteRefundParams) => { 133 | try { 134 | const response = await makeWooCommerceRequest('DELETE', `orders/${params.order_id}/refunds/${params.id}`, { force: params.force }); 135 | const refund: WooCommerceRefund = response; 136 | return { 137 | toolResult: { 138 | content: [{ type: 'text', text: JSON.stringify(refund, null, 2) }], 139 | }, 140 | }; 141 | } catch (error: any) { 142 | const errorMessage = error.response?.data?.message || error.message; 143 | return { 144 | toolResult: { 145 | isError: true, 146 | content: [{ type: 'text', text: `Error deleting refund: ${errorMessage}` }], 147 | }, 148 | }; 149 | } 150 | } 151 | }; -------------------------------------------------------------------------------- /mcp/woo/src/tools/shipping.ts: -------------------------------------------------------------------------------- 1 | import { Tool } from '@modelcontextprotocol/sdk/types.js'; 2 | import { makeWooCommerceRequest } from '../woocommerce.js'; 3 | import { z } from 'zod'; 4 | import { zodToJsonSchema } from 'zod-to-json-schema'; 5 | import { WooCommerceShippingLine } from '../types/woocommerce-types.js'; 6 | 7 | const listShippingZonesSchema = z.object({}); 8 | 9 | const createShippingZoneSchema = z.object({ 10 | name: z.string().describe("Shipping zone name"), 11 | order: z.number().optional().describe("Shipping zone order") 12 | }); 13 | 14 | const getShippingZoneSchema = z.object({ 15 | id: z.number().describe("Shipping zone ID") 16 | }); 17 | 18 | const updateShippingZoneSchema = z.object({ 19 | id: z.number().describe("Shipping zone ID"), 20 | name: z.string().optional().describe("Shipping zone name"), 21 | order: z.number().optional().describe("Shipping zone order") 22 | }); 23 | 24 | const deleteShippingZoneSchema = z.object({ 25 | id: z.number().describe("Shipping zone ID"), 26 | force: z.boolean().optional().describe("Required to be true, as resource does not support trashing") 27 | }); 28 | 29 | const listShippingMethodsSchema = z.object({ 30 | zone_id: z.number().describe("Shipping zone ID") 31 | }); 32 | 33 | const addShippingMethodSchema = z.object({ 34 | zone_id: z.number().describe("Shipping zone ID"), 35 | method_id: z.string().describe("Shipping method ID (flat_rate, free_shipping, local_pickup, etc)"), 36 | title: z.string().optional().describe("Shipping method title"), 37 | order: z.number().optional().describe("Shipping method order"), 38 | enabled: z.boolean().optional().describe("Whether the shipping method is enabled"), 39 | settings: z.record(z.any()).optional().describe("Shipping method settings") 40 | }); 41 | 42 | const updateShippingMethodSchema = z.object({ 43 | zone_id: z.number().describe("Shipping zone ID"), 44 | instance_id: z.number().describe("Shipping method instance ID"), 45 | title: z.string().optional().describe("Shipping method title"), 46 | order: z.number().optional().describe("Shipping method order"), 47 | enabled: z.boolean().optional().describe("Whether the shipping method is enabled"), 48 | settings: z.record(z.any()).optional().describe("Shipping method settings") 49 | }); 50 | 51 | const deleteShippingMethodSchema = z.object({ 52 | zone_id: z.number().describe("Shipping zone ID"), 53 | instance_id: z.number().describe("Shipping method instance ID"), 54 | force: z.boolean().optional().describe("Required to be true, as resource does not support trashing") 55 | }); 56 | 57 | type ListShippingZonesParams = z.infer; 58 | type CreateShippingZoneParams = z.infer; 59 | type GetShippingZoneParams = z.infer; 60 | type UpdateShippingZoneParams = z.infer; 61 | type DeleteShippingZoneParams = z.infer; 62 | type ListShippingMethodsParams = z.infer; 63 | type AddShippingMethodParams = z.infer; 64 | type UpdateShippingMethodParams = z.infer; 65 | type DeleteShippingMethodParams = z.infer; 66 | 67 | export const shippingTools: Tool[] = [ 68 | { 69 | name: "list_shipping_zones", 70 | description: "Lists all shipping zones", 71 | inputSchema: zodToJsonSchema(listShippingZonesSchema) as { type: "object"; properties: Record } 72 | }, 73 | { 74 | name: "create_shipping_zone", 75 | description: "Creates a new shipping zone", 76 | inputSchema: zodToJsonSchema(createShippingZoneSchema) as { type: "object"; properties: Record } 77 | }, 78 | { 79 | name: "get_shipping_zone", 80 | description: "Gets a shipping zone by ID", 81 | inputSchema: zodToJsonSchema(getShippingZoneSchema) as { type: "object"; properties: Record } 82 | }, 83 | { 84 | name: "update_shipping_zone", 85 | description: "Updates an existing shipping zone", 86 | inputSchema: zodToJsonSchema(updateShippingZoneSchema) as { type: "object"; properties: Record } 87 | }, 88 | { 89 | name: "delete_shipping_zone", 90 | description: "Deletes a shipping zone", 91 | inputSchema: zodToJsonSchema(deleteShippingZoneSchema) as { type: "object"; properties: Record } 92 | }, 93 | { 94 | name: "list_shipping_methods", 95 | description: "Lists all shipping methods for a zone", 96 | inputSchema: zodToJsonSchema(listShippingMethodsSchema) as { type: "object"; properties: Record } 97 | }, 98 | { 99 | name: "add_shipping_method", 100 | description: "Adds a shipping method to a zone", 101 | inputSchema: zodToJsonSchema(addShippingMethodSchema) as { type: "object"; properties: Record } 102 | }, 103 | { 104 | name: "update_shipping_method", 105 | description: "Updates a shipping method in a zone", 106 | inputSchema: zodToJsonSchema(updateShippingMethodSchema) as { type: "object"; properties: Record } 107 | }, 108 | { 109 | name: "delete_shipping_method", 110 | description: "Deletes a shipping method from a zone", 111 | inputSchema: zodToJsonSchema(deleteShippingMethodSchema) as { type: "object"; properties: Record } 112 | } 113 | ]; 114 | 115 | export const shippingHandlers = { 116 | list_shipping_zones: async () => { 117 | try { 118 | const response = await makeWooCommerceRequest('GET', "shipping/zones"); 119 | const shippingZones: WooCommerceShippingLine[] = response; 120 | return { 121 | toolResult: { 122 | content: [{ type: 'text', text: JSON.stringify(shippingZones, null, 2) }], 123 | }, 124 | }; 125 | } catch (error: any) { 126 | const errorMessage = error.response?.data?.message || error.message; 127 | return { 128 | toolResult: { 129 | isError: true, 130 | content: [{ type: 'text', text: `Error listing shipping zones: ${errorMessage}` }], 131 | }, 132 | }; 133 | } 134 | }, 135 | create_shipping_zone: async (params: CreateShippingZoneParams) => { 136 | try { 137 | const response = await makeWooCommerceRequest('POST', "shipping/zones", params); 138 | const shippingZone: WooCommerceShippingLine = response; 139 | return { 140 | toolResult: { 141 | content: [{ type: 'text', text: JSON.stringify(shippingZone, null, 2) }], 142 | }, 143 | }; 144 | } catch (error: any) { 145 | const errorMessage = error.response?.data?.message || error.message; 146 | return { 147 | toolResult: { 148 | isError: true, 149 | content: [{ type: 'text', text: `Error creating shipping zone: ${errorMessage}` }], 150 | }, 151 | }; 152 | } 153 | }, 154 | get_shipping_zone: async (params: GetShippingZoneParams) => { 155 | try { 156 | const response = await makeWooCommerceRequest('GET', `shipping/zones/${params.id}`); 157 | const shippingZone: WooCommerceShippingLine = response; 158 | return { 159 | toolResult: { 160 | content: [{ type: 'text', text: JSON.stringify(shippingZone, null, 2) }], 161 | }, 162 | }; 163 | } catch (error: any) { 164 | const errorMessage = error.response?.data?.message || error.message; 165 | return { 166 | toolResult: { 167 | isError: true, 168 | content: [{ type: 'text', text: `Error getting shipping zone: ${errorMessage}` }], 169 | }, 170 | }; 171 | } 172 | }, 173 | update_shipping_zone: async (params: UpdateShippingZoneParams) => { 174 | try { 175 | const { id, ...updateData } = params; 176 | const response = await makeWooCommerceRequest('PUT', `shipping/zones/${id}`, updateData); 177 | const shippingZone: WooCommerceShippingLine = response; 178 | return { 179 | toolResult: { 180 | content: [{ type: 'text', text: JSON.stringify(shippingZone, null, 2) }], 181 | }, 182 | }; 183 | } catch (error: any) { 184 | const errorMessage = error.response?.data?.message || error.message; 185 | return { 186 | toolResult: { 187 | isError: true, 188 | content: [{ type: 'text', text: `Error updating shipping zone: ${errorMessage}` }], 189 | }, 190 | }; 191 | } 192 | }, 193 | delete_shipping_zone: async (params: DeleteShippingZoneParams) => { 194 | try { 195 | const response = await makeWooCommerceRequest('DELETE', `shipping/zones/${params.id}`, { force: params.force }); 196 | const shippingZone: WooCommerceShippingLine = response; 197 | return { 198 | toolResult: { 199 | content: [{ type: 'text', text: JSON.stringify(shippingZone, null, 2) }], 200 | }, 201 | }; 202 | } catch (error: any) { 203 | const errorMessage = error.response?.data?.message || error.message; 204 | return { 205 | toolResult: { 206 | isError: true, 207 | content: [{ type: 'text', text: `Error deleting shipping zone: ${errorMessage}` }], 208 | }, 209 | }; 210 | } 211 | }, 212 | list_shipping_methods: async (params: ListShippingMethodsParams) => { 213 | try { 214 | const response = await makeWooCommerceRequest('GET', `shipping/zones/${params.zone_id}/methods`); 215 | const shippingMethods: WooCommerceShippingLine[] = response; 216 | return { 217 | toolResult: { 218 | content: [{ type: 'text', text: JSON.stringify(shippingMethods, null, 2) }], 219 | }, 220 | }; 221 | } catch (error: any) { 222 | const errorMessage = error.response?.data?.message || error.message; 223 | return { 224 | toolResult: { 225 | isError: true, 226 | content: [{ type: 'text', text: `Error listing shipping methods: ${errorMessage}` }], 227 | }, 228 | }; 229 | } 230 | }, 231 | add_shipping_method: async (params: AddShippingMethodParams) => { 232 | try { 233 | const { zone_id, ...methodData } = params; 234 | const response = await makeWooCommerceRequest('POST', `shipping/zones/${zone_id}/methods`, methodData); 235 | const shippingMethod: WooCommerceShippingLine = response; 236 | return { 237 | toolResult: { 238 | content: [{ type: 'text', text: JSON.stringify(shippingMethod, null, 2) }], 239 | }, 240 | }; 241 | } catch (error: any) { 242 | const errorMessage = error.response?.data?.message || error.message; 243 | return { 244 | toolResult: { 245 | isError: true, 246 | content: [{ type: 'text', text: `Error adding shipping method: ${errorMessage}` }], 247 | }, 248 | }; 249 | } 250 | }, 251 | update_shipping_method: async (params: UpdateShippingMethodParams) => { 252 | try { 253 | const { zone_id, instance_id, ...updateData } = params; 254 | const response = await makeWooCommerceRequest('PUT', `shipping/zones/${zone_id}/methods/${instance_id}`, updateData); 255 | const shippingMethod: WooCommerceShippingLine = response; 256 | return { 257 | toolResult: { 258 | content: [{ type: 'text', text: JSON.stringify(shippingMethod, null, 2) }], 259 | }, 260 | }; 261 | } catch (error: any) { 262 | const errorMessage = error.response?.data?.message || error.message; 263 | return { 264 | toolResult: { 265 | isError: true, 266 | content: [{ type: 'text', text: `Error updating shipping method: ${errorMessage}` }], 267 | }, 268 | }; 269 | } 270 | }, 271 | delete_shipping_method: async (params: DeleteShippingMethodParams) => { 272 | try { 273 | const response = await makeWooCommerceRequest('DELETE', `shipping/zones/${params.zone_id}/methods/${params.instance_id}`, { force: params.force }); 274 | const shippingMethod: WooCommerceShippingLine = response; 275 | return { 276 | toolResult: { 277 | content: [{ type: 'text', text: JSON.stringify(shippingMethod, null, 2) }], 278 | }, 279 | }; 280 | } catch (error: any) { 281 | const errorMessage = error.response?.data?.message || error.message; 282 | return { 283 | toolResult: { 284 | isError: true, 285 | content: [{ type: 'text', text: `Error deleting shipping method: ${errorMessage}` }], 286 | }, 287 | }; 288 | } 289 | } 290 | }; -------------------------------------------------------------------------------- /mcp/woo/src/tools/tax_rates.ts: -------------------------------------------------------------------------------- 1 | import { Tool } from '@modelcontextprotocol/sdk/types.js'; 2 | import { makeWooCommerceRequest } from '../woocommerce.js'; 3 | import { z } from 'zod'; 4 | import { zodToJsonSchema } from 'zod-to-json-schema'; 5 | import { WooCommerceTax } from '../types/woocommerce-types.js'; 6 | 7 | const listTaxRatesSchema = z.object({ 8 | page: z.number().optional().describe("Page number (default 1)"), 9 | per_page: z.number().min(1).max(100).optional().describe("Items per page (default 10, max 100)"), 10 | offset: z.number().optional().describe("Offset the result set by a specific number of items"), 11 | order: z.enum(['asc', 'desc']).optional().describe("Order sort attribute ascending or descending"), 12 | orderby: z.enum(['id', 'order', 'priority']).optional().describe("Sort by parameter") 13 | }); 14 | 15 | const createTaxRateSchema = z.object({ 16 | country: z.string().describe("Country code in ISO 3166-1 alpha-2 format"), 17 | state: z.string().optional().describe("State code"), 18 | postcode: z.string().optional().describe("Postcode/ZIP"), 19 | city: z.string().optional().describe("City name"), 20 | rate: z.string().describe("Tax rate"), 21 | name: z.string().describe("Tax rate name"), 22 | priority: z.number().optional().describe("Tax priority"), 23 | compound: z.boolean().optional().describe("Whether or not this is a compound rate"), 24 | shipping: z.boolean().optional().describe("Whether or not this tax rate applies to shipping") 25 | }); 26 | 27 | const getTaxRateSchema = z.object({ 28 | id: z.number().describe("Tax rate ID") 29 | }); 30 | 31 | const updateTaxRateSchema = createTaxRateSchema.extend({ 32 | id: z.number().describe("Tax rate ID") 33 | }).partial(); 34 | 35 | const deleteTaxRateSchema = z.object({ 36 | id: z.number().describe("Tax rate ID"), 37 | force: z.boolean().optional().describe("Required to be true, as resource does not support trashing") 38 | }); 39 | 40 | type ListTaxRatesParams = z.infer; 41 | type CreateTaxRateParams = z.infer; 42 | type GetTaxRateParams = z.infer; 43 | type UpdateTaxRateParams = z.infer; 44 | type DeleteTaxRateParams = z.infer; 45 | 46 | export const taxRateTools: Tool[] = [ 47 | { 48 | name: "list_tax_rates", 49 | description: "Lists all tax rates with filtering, sorting, and pagination options", 50 | inputSchema: zodToJsonSchema(listTaxRatesSchema) as { type: "object"; properties: Record } 51 | }, 52 | { 53 | name: "create_tax_rate", 54 | description: "Creates a new tax rate", 55 | inputSchema: zodToJsonSchema(createTaxRateSchema) as { type: "object"; properties: Record } 56 | }, 57 | { 58 | name: "get_tax_rate", 59 | description: "Gets a tax rate by ID", 60 | inputSchema: zodToJsonSchema(getTaxRateSchema) as { type: "object"; properties: Record } 61 | }, 62 | { 63 | name: "update_tax_rate", 64 | description: "Updates an existing tax rate", 65 | inputSchema: zodToJsonSchema(updateTaxRateSchema) as { type: "object"; properties: Record } 66 | }, 67 | { 68 | name: "delete_tax_rate", 69 | description: "Deletes a tax rate", 70 | inputSchema: zodToJsonSchema(deleteTaxRateSchema) as { type: "object"; properties: Record } 71 | } 72 | ]; 73 | 74 | export const taxRateHandlers = { 75 | list_tax_rates: async (params: ListTaxRatesParams) => { 76 | try { 77 | const response = await makeWooCommerceRequest('GET', "taxes", params); 78 | const taxRates: WooCommerceTax[] = response; 79 | return { 80 | toolResult: { 81 | content: [{ type: 'text', text: JSON.stringify(taxRates, null, 2) }], 82 | }, 83 | }; 84 | } catch (error: any) { 85 | const errorMessage = error.response?.data?.message || error.message; 86 | return { 87 | toolResult: { 88 | isError: true, 89 | content: [{ type: 'text', text: `Error listing tax rates: ${errorMessage}` }], 90 | }, 91 | }; 92 | } 93 | }, 94 | create_tax_rate: async (params: CreateTaxRateParams) => { 95 | try { 96 | const response = await makeWooCommerceRequest('POST', "taxes", params); 97 | const taxRate: WooCommerceTax = response; 98 | return { 99 | toolResult: { 100 | content: [{ type: 'text', text: JSON.stringify(taxRate, null, 2) }], 101 | }, 102 | }; 103 | } catch (error: any) { 104 | const errorMessage = error.response?.data?.message || error.message; 105 | return { 106 | toolResult: { 107 | isError: true, 108 | content: [{ type: 'text', text: `Error creating tax rate: ${errorMessage}` }], 109 | }, 110 | }; 111 | } 112 | }, 113 | get_tax_rate: async (params: GetTaxRateParams) => { 114 | try { 115 | const response = await makeWooCommerceRequest('GET', `taxes/${params.id}`); 116 | const taxRate: WooCommerceTax = response; 117 | return { 118 | toolResult: { 119 | content: [{ type: 'text', text: JSON.stringify(taxRate, null, 2) }], 120 | }, 121 | }; 122 | } catch (error: any) { 123 | const errorMessage = error.response?.data?.message || error.message; 124 | return { 125 | toolResult: { 126 | isError: true, 127 | content: [{ type: 'text', text: `Error getting tax rate: ${errorMessage}` }], 128 | }, 129 | }; 130 | } 131 | }, 132 | update_tax_rate: async (params: UpdateTaxRateParams) => { 133 | try { 134 | const { id, ...updateData } = params; 135 | const response = await makeWooCommerceRequest('PUT', `taxes/${id}`, updateData); 136 | const taxRate: WooCommerceTax = response; 137 | return { 138 | toolResult: { 139 | content: [{ type: 'text', text: JSON.stringify(taxRate, null, 2) }], 140 | }, 141 | }; 142 | } catch (error: any) { 143 | const errorMessage = error.response?.data?.message || error.message; 144 | return { 145 | toolResult: { 146 | isError: true, 147 | content: [{ type: 'text', text: `Error updating tax rate: ${errorMessage}` }], 148 | }, 149 | }; 150 | } 151 | }, 152 | delete_tax_rate: async (params: DeleteTaxRateParams) => { 153 | try { 154 | const response = await makeWooCommerceRequest('DELETE', `taxes/${params.id}`, { force: params.force }); 155 | const taxRate: WooCommerceTax = response; 156 | return { 157 | toolResult: { 158 | content: [{ type: 'text', text: JSON.stringify(taxRate, null, 2) }], 159 | }, 160 | }; 161 | } catch (error: any) { 162 | const errorMessage = error.response?.data?.message || error.message; 163 | return { 164 | toolResult: { 165 | isError: true, 166 | content: [{ type: 'text', text: `Error deleting tax rate: ${errorMessage}` }], 167 | }, 168 | }; 169 | } 170 | } 171 | }; -------------------------------------------------------------------------------- /mcp/woo/src/types/woocommerce-types.ts: -------------------------------------------------------------------------------- 1 | // src/types/woocommerce-types.ts 2 | 3 | /** 4 | * This file defines TypeScript interfaces for WooCommerce resources. 5 | * These types are used throughout the application to ensure consistency 6 | * and provide compile-time checking of API responses. 7 | */ 8 | 9 | /* ====================== 10 | WooCommerce Coupon 11 | ====================== */ 12 | export interface WooCommerceCoupon { 13 | id: number; 14 | code: string; 15 | amount: string; 16 | discount_type: string; 17 | description: string; 18 | date_expires?: string; 19 | individual_use?: boolean; 20 | product_ids?: number[]; 21 | excluded_product_ids?: number[]; 22 | usage_limit?: number; 23 | usage_limit_per_user?: number; 24 | limit_usage_to_x_items?: number; 25 | free_shipping?: boolean; 26 | product_categories?: number[]; 27 | excluded_product_categories?: number[]; 28 | exclude_sale_items?: boolean; 29 | minimum_amount?: string; 30 | maximum_amount?: string; 31 | email_restrictions?: string[]; 32 | meta_data?: WooCommerceMetaData[]; 33 | } 34 | 35 | /* ====================== 36 | WooCommerce Customer 37 | ====================== */ 38 | export interface WooCommerceCustomer { 39 | id: number; 40 | email: string; 41 | first_name: string; 42 | last_name: string; 43 | username?: string; 44 | billing?: WooCommerceAddress; 45 | shipping?: WooCommerceAddress; 46 | meta_data?: WooCommerceMetaData[]; 47 | date_created?: string; 48 | date_modified?: string; 49 | } 50 | 51 | /* ====================== 52 | WooCommerce Order 53 | ====================== */ 54 | export interface WooCommerceOrder { 55 | id: number; 56 | order_number: string; // Sometimes exposed as a string 57 | status: string; 58 | currency: string; 59 | total: string; 60 | subtotal: string; 61 | total_tax: string; 62 | shipping_total: string; 63 | payment_method: string; 64 | payment_method_title: string; 65 | transaction_id?: string; 66 | customer_id?: number; 67 | billing: WooCommerceAddress; 68 | shipping: WooCommerceAddress; 69 | line_items: WooCommerceOrderLineItem[]; 70 | shipping_lines: WooCommerceShippingLine[]; 71 | fee_lines?: WooCommerceFeeLine[]; 72 | coupon_lines?: WooCommerceCouponLine[]; 73 | refunds?: WooCommerceRefund[]; 74 | meta_data?: WooCommerceMetaData[]; 75 | date_created: string; 76 | date_modified: string; 77 | } 78 | 79 | /* ====================== 80 | WooCommerce Product 81 | ====================== */ 82 | export interface WooCommerceProduct { 83 | id: number; 84 | name: string; 85 | slug: string; 86 | permalink: string; 87 | date_created: string; 88 | date_modified: string; 89 | type: string; 90 | status: string; 91 | featured: boolean; 92 | catalog_visibility: string; 93 | description: string; 94 | short_description: string; 95 | sku: string; 96 | price: string; 97 | regular_price: string; 98 | sale_price: string; 99 | date_on_sale_from?: string; 100 | date_on_sale_to?: string; 101 | price_html: string; 102 | related_ids: number[]; 103 | meta_data: WooCommerceMetaData[]; 104 | stock_quantity: number | null; 105 | stock_status: string; 106 | weight: string; 107 | dimensions: WooCommerceDimensions; 108 | shipping_required: boolean; 109 | shipping_taxable: boolean; 110 | shipping_class: string; 111 | reviews_allowed: boolean; 112 | average_rating: string; 113 | rating_count: number; 114 | upsell_ids: number[]; 115 | cross_sell_ids: number[]; 116 | parent_id: number; 117 | purchase_note: string; 118 | categories: WooCommerceProductCategory[]; 119 | tags: WooCommerceProductTag[]; 120 | images: WooCommerceProductImage[]; 121 | attributes: WooCommerceProductAttribute[]; 122 | default_attributes: WooCommerceProductDefaultAttribute[]; 123 | variations: number[]; 124 | grouped_products: number[]; 125 | menu_order: number; 126 | } 127 | 128 | /* ====================== 129 | WooCommerce Product Variation 130 | ====================== */ 131 | export interface WooCommerceProductVariation { 132 | id: number; 133 | date_created: string; 134 | date_modified: string; 135 | description: string; 136 | permalink: string; 137 | sku: string; 138 | price: string; 139 | regular_price: string; 140 | sale_price: string; 141 | date_on_sale_from?: string; 142 | date_on_sale_to?: string; 143 | on_sale: boolean; 144 | status: string; 145 | purchasable: boolean; 146 | virtual: boolean; 147 | downloadable: boolean; 148 | downloads: Array<{ 149 | id: string; 150 | name: string; 151 | file: string; 152 | }>; 153 | download_limit: number; 154 | download_expiry: number; 155 | tax_status: string; 156 | tax_class: string; 157 | manage_stock: boolean; 158 | stock_quantity: number | null; 159 | stock_status: string; 160 | backorders: string; 161 | backorders_allowed: boolean; 162 | backordered: boolean; 163 | weight: string; 164 | dimensions: WooCommerceDimensions; 165 | shipping_class: string; 166 | shipping_class_id: number; 167 | image: WooCommerceProductImage; 168 | attributes: WooCommerceProductDefaultAttribute[]; 169 | menu_order: number; 170 | meta_data: WooCommerceMetaData[]; 171 | } 172 | 173 | /* ====================== 174 | Supporting Interfaces 175 | ====================== */ 176 | 177 | /** Common metadata field for most resources */ 178 | export interface WooCommerceMetaData { 179 | id?: number; 180 | key: string; 181 | value: any; 182 | } 183 | 184 | /** Address format used for billing and shipping */ 185 | export interface WooCommerceAddress { 186 | first_name?: string; 187 | last_name?: string; 188 | company?: string; 189 | address_1?: string; 190 | address_2?: string; 191 | city?: string; 192 | state?: string; 193 | postcode?: string; 194 | country?: string; 195 | email?: string; 196 | phone?: string; 197 | } 198 | 199 | /** Order line item */ 200 | export interface WooCommerceOrderLineItem { 201 | id: number; 202 | name: string; 203 | product_id: number; 204 | variation_id: number; 205 | quantity: number; 206 | tax_class: string; 207 | subtotal: string; 208 | subtotal_tax: string; 209 | total: string; 210 | total_tax: string; 211 | taxes: WooCommerceTax[]; 212 | meta_data: WooCommerceMetaData[]; 213 | } 214 | 215 | /** Shipping line for an order */ 216 | export interface WooCommerceShippingLine { 217 | id: number; 218 | method_title: string; 219 | method_id: string; 220 | total: string; 221 | total_tax: string; 222 | taxes: WooCommerceTax[]; 223 | } 224 | 225 | /** Fee line on an order (if applicable) */ 226 | export interface WooCommerceFeeLine { 227 | id: number; 228 | name: string; 229 | tax_class: string; 230 | tax_status: string; 231 | amount: string; 232 | total: string; 233 | total_tax: string; 234 | taxes: WooCommerceTax[]; 235 | meta_data: WooCommerceMetaData[]; 236 | } 237 | 238 | /** Coupon line applied to an order */ 239 | export interface WooCommerceCouponLine { 240 | id: number; 241 | code: string; 242 | discount: string; 243 | discount_tax: string; 244 | } 245 | 246 | /** A refund issued for an order */ 247 | export interface WooCommerceRefund { 248 | id: number; 249 | reason: string; 250 | total: string; 251 | meta_data: WooCommerceMetaData[]; 252 | } 253 | 254 | /** Tax details for order items, fees, or shipping */ 255 | export interface WooCommerceTax { 256 | id: number; 257 | total: string; 258 | subtotal: string; 259 | } 260 | 261 | /** Product dimensions */ 262 | export interface WooCommerceDimensions { 263 | length: string; 264 | width: string; 265 | height: string; 266 | } 267 | 268 | /** Product category */ 269 | export interface WooCommerceProductCategory { 270 | id: number; 271 | name: string; 272 | slug: string; 273 | } 274 | 275 | /** Product tag */ 276 | export interface WooCommerceProductTag { 277 | id: number; 278 | name: string; 279 | slug: string; 280 | } 281 | 282 | /** Product image */ 283 | export interface WooCommerceProductImage { 284 | id: number; 285 | src: string; 286 | name: string; 287 | alt: string; 288 | } 289 | 290 | /** Product attribute (e.g. color, size) */ 291 | export interface WooCommerceProductAttribute { 292 | id: number; 293 | name: string; 294 | position: number; 295 | visible: boolean; 296 | variation: boolean; 297 | options: string[]; 298 | } 299 | 300 | /** Default attribute value for a variable product */ 301 | export interface WooCommerceProductDefaultAttribute { 302 | id: number; 303 | name: string; 304 | option: string; 305 | } 306 | 307 | /* ====================== 308 | WooCommerce Payment Gateway 309 | ====================== */ 310 | export interface WooCommercePaymentGateway { 311 | id: string; 312 | title: string; 313 | description: string; 314 | order: number; 315 | enabled: boolean; 316 | method_title: string; 317 | method_description: string; 318 | settings: Record; 319 | } 320 | -------------------------------------------------------------------------------- /mcp/woo/src/woocommerce.ts: -------------------------------------------------------------------------------- 1 | // src/woocommerce.ts 2 | import axios, { AxiosInstance } from 'axios'; 3 | import * as https from 'https'; 4 | import * as crypto from 'crypto'; 5 | 6 | let client: AxiosInstance; 7 | 8 | export function initWooCommerce() { 9 | const baseURL = process.env.WOOCOMMERCE_API_URL || 'your_woocommerce_store_url.com'; 10 | const consumerKey = process.env.WOOCOMMERCE_CONSUMER_KEY || 'your_consumer_key'; 11 | const consumerSecret = process.env.WOOCOMMERCE_CONSUMER_SECRET || 'your_consumer_secret'; 12 | const isInsecure = process.env.WOOCOMMERCE_INSECURE_HTTP === 'true'; 13 | 14 | client = axios.create({ 15 | baseURL: `${baseURL}/wp-json/wc/v3`, 16 | httpsAgent: isInsecure ? new https.Agent({ rejectUnauthorized: false }) : undefined, 17 | }); 18 | 19 | // Add request interceptor for WooCommerce authentication 20 | client.interceptors.request.use((config) => { 21 | const timestamp = Math.floor(Date.now() / 1000); 22 | const nonce = crypto.randomBytes(8).toString('hex'); 23 | 24 | // Add authentication parameters to URL 25 | const params = new URLSearchParams(config.params || {}); 26 | params.append('consumer_key', consumerKey); 27 | params.append('consumer_secret', consumerSecret); 28 | config.params = params; 29 | 30 | return config; 31 | }); 32 | } 33 | 34 | // Initialize WooCommerce when this module loads 35 | initWooCommerce(); 36 | 37 | // Add a helper to make authenticated requests 38 | export async function makeWooCommerceRequest(method: 'GET' | 'POST' | 'PUT' | 'DELETE', endpoint: string, params?: any) { 39 | try { 40 | const response = await client.request({ 41 | method, 42 | url: endpoint, 43 | ...(method === 'GET' ? { params } : { data: params }), 44 | }); 45 | return response.data; 46 | } catch (error: any) { 47 | console.error(`WooCommerce API Error (${method} ${endpoint}):`, error.response ? error.response.data : error.message); 48 | throw new Error(`WooCommerce API Error: ${error.message}`); 49 | } 50 | } -------------------------------------------------------------------------------- /mcp/woo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "ESNext", 5 | "moduleResolution": "node", 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "strict": true, 9 | "skipLibCheck": true, 10 | "outDir": "./build", 11 | "rootDir": "./src", 12 | "declaration": true 13 | }, 14 | "include": ["src/**/*"], 15 | "exclude": ["node_modules", "build"] 16 | } -------------------------------------------------------------------------------- /plugins/llms-txt/README.md: -------------------------------------------------------------------------------- 1 | # LLMS TXT - Plugin to easily generate LLM-friendly text files 2 | 3 | A WordPress plugin that provides structured text endpoints for exposing your site's content to LLMs. 4 | 5 | ## Features 6 | 7 | - Provides three dedicated endpoints for accessing site content: 8 | - `/llms.txt` - A concise listing of all available content (like a table of contents) 9 | - `/llms-full.txt` - Complete content of all included posts/pages in a Markdown-like format 10 | - `/llms-small.txt` - Condensed version with excerpts of all included content 11 | 12 | - Admin settings page under "Settings > LLMS TXT" to configure: 13 | - Which post types to include in the endpoints 14 | - Default includes posts and pages 15 | 16 | - Built-in performance optimization: 17 | - Content is cached using WordPress transients 18 | - Cache refreshes hourly to ensure content stays current 19 | - Efficient query handling for large sites 20 | 21 | ## Installation 22 | 23 | 1. Upload the `llms-txt` directory to your `/wp-content/plugins/` directory 24 | 2. Activate the plugin through the 'Plugins' menu in WordPress 25 | 3. Configure which post types to include via Settings > LLMS TXT 26 | 27 | ## Usage 28 | 29 | After activation, the plugin automatically sets up the following endpoints: 30 | 31 | - `https://your-site.com/llms.txt` - Get a listing of all available content 32 | - `https://your-site.com/llms-full.txt` - Get full content in plain text format 33 | - `https://your-site.com/llms-small.txt` - Get excerpts of all content 34 | 35 | These endpoints return plain text responses suitable for consumption by LLMs or other text processing tools. 36 | 37 | ## Configuration 38 | 39 | 1. Go to Settings > LLMS TXT in your WordPress admin panel 40 | 2. Select which post types you want to include in the endpoints 41 | 3. Save your settings 42 | 4. The endpoints will automatically update to reflect your choices 43 | 44 | ## Technical Details 45 | 46 | - Content is served as plain text with UTF-8 encoding 47 | - URLs and titles are preserved in a Markdown-compatible format 48 | - HTML is stripped from content for clean text output 49 | - Custom post types can be included via the settings page 50 | - Cached content refreshes automatically every hour 51 | 52 | ## Requirements 53 | 54 | - WordPress 5.0 or higher 55 | - PHP 7.0 or higher 56 | 57 | ## License 58 | 59 | GPL-2.0+ 60 | 61 | ## Author 62 | 63 | James LePage - Automattic AI -------------------------------------------------------------------------------- /plugins/llms-txt/includes/class-llms-txt-admin.php: -------------------------------------------------------------------------------- 1 | LLMS TXT 19 | */ 20 | public function register_admin_menu() { 21 | add_options_page( 22 | 'LLMS TXT Settings', 23 | 'LLMS TXT', 24 | 'manage_options', 25 | 'llms-txt-settings', 26 | [ $this, 'render_settings_page' ] 27 | ); 28 | } 29 | 30 | /** 31 | * Register a setting that stores user options for which post types we include, etc. 32 | */ 33 | public function register_settings() { 34 | register_setting( 35 | 'llms_txt_plugin_group', 36 | self::OPTION_KEY, 37 | [ 'sanitize_callback' => [ $this, 'sanitize_options' ] ] 38 | ); 39 | 40 | add_settings_section( 41 | 'llms_txt_section', 42 | 'LLMS TXT Configuration', 43 | function() { 44 | echo '

Configure how LLMS TXT plugin exposes your site content to LLMs.

'; 45 | }, 46 | 'llms-txt-settings' 47 | ); 48 | 49 | add_settings_field( 50 | 'include_post_types', 51 | 'Include Post Types', 52 | [ $this, 'render_include_post_types_field' ], 53 | 'llms-txt-settings', 54 | 'llms_txt_section' 55 | ); 56 | } 57 | 58 | /** 59 | * Render the checkbox list for picking which post types to expose. 60 | */ 61 | public function render_include_post_types_field() { 62 | $options = $this->get_options(); 63 | $selected_types = isset($options['include_post_types']) ? (array)$options['include_post_types'] : []; 64 | 65 | $public_types = get_post_types( [ 'public' => true ], 'objects' ); 66 | echo '
    '; 67 | foreach( $public_types as $ptype => $pobj ) { 68 | $checked = in_array( $ptype, $selected_types ) ? 'checked' : ''; 69 | printf( 70 | '
  • ', 71 | esc_attr( self::OPTION_KEY . '[include_post_types]' ), 72 | esc_attr( $ptype ), 73 | $checked, 74 | esc_html( $pobj->labels->name ) 75 | ); 76 | } 77 | echo '
'; 78 | } 79 | 80 | /** 81 | * Display the settings page HTML 82 | */ 83 | public function render_settings_page() { 84 | ?> 85 |
86 |

LLMS TXT Settings

87 |
88 | 93 |
94 | 95 | 104 |
105 | true ] ); 114 | if ( isset( $input['include_post_types'] ) && is_array( $input['include_post_types'] ) ) { 115 | // Only keep the ones that are valid 116 | $filtered = array_filter( $input['include_post_types'], function( $pt ) use ( $all_public ) { 117 | return in_array( $pt, $all_public, true ); 118 | } ); 119 | $clean['include_post_types'] = array_values( $filtered ); 120 | } else { 121 | // Default to just 'post' and 'page' 122 | $clean['include_post_types'] = [ 'post', 'page' ]; 123 | } 124 | return $clean; 125 | } 126 | 127 | /** 128 | * Helper to get our plugin options from DB 129 | */ 130 | public function get_options() { 131 | $defaults = [ 132 | 'include_post_types' => [ 'post', 'page' ], 133 | ]; 134 | $saved = get_option( self::OPTION_KEY, [] ); 135 | return wp_parse_args( $saved, $defaults ); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /plugins/llms-txt/includes/class-llms-txt-content.php: -------------------------------------------------------------------------------- 1 | labels->singular_name; 31 | 32 | // Get taxonomies and terms 33 | $taxonomies = get_object_taxonomies($pid); 34 | $tax_terms = []; 35 | foreach ($taxonomies as $tax) { 36 | $terms = wp_get_post_terms($pid, $tax); 37 | if (!empty($terms) && !is_wp_error($terms)) { 38 | $term_names = wp_list_pluck($terms, 'name'); 39 | $tax_obj = get_taxonomy($tax); 40 | $tax_terms[] = $tax_obj->labels->singular_name . ': ' . implode(', ', $term_names); 41 | } 42 | } 43 | 44 | $meta = " ({$post_type}"; 45 | if (!empty($tax_terms)) { 46 | $meta .= " | " . implode(' | ', $tax_terms); 47 | } 48 | $meta .= ")"; 49 | 50 | $lines[] = "- [{$title}{$meta}]({$link})"; 51 | } 52 | $lines[] = ''; 53 | 54 | $output = implode( "\n", $lines ); 55 | set_transient( $cache_key, $output, HOUR_IN_SECONDS ); 56 | return $output; 57 | } 58 | 59 | /** 60 | * Return the full content for each included post, in a rough Markdown format. 61 | */ 62 | public static function get_full_text() { 63 | $cache_key = self::CACHE_KEY_PREFIX . 'full'; 64 | $cached = get_transient( $cache_key ); 65 | if ( false !== $cached ) { 66 | return $cached; 67 | } 68 | 69 | $lines = []; 70 | $lines[] = '# LLMS Full Content'; 71 | $lines[] = ''; 72 | 73 | $post_ids = self::fetch_all_included_posts(); 74 | foreach ( $post_ids as $pid ) { 75 | $title = get_the_title( $pid ); 76 | $content = get_post( $pid )->post_content; 77 | $post_type = get_post_type_object(get_post_type($pid))->labels->singular_name; 78 | 79 | // Get taxonomies and terms 80 | $taxonomies = get_object_taxonomies($pid); 81 | $tax_terms = []; 82 | foreach ($taxonomies as $tax) { 83 | $terms = wp_get_post_terms($pid, $tax); 84 | if (!empty($terms) && !is_wp_error($terms)) { 85 | $term_names = wp_list_pluck($terms, 'name'); 86 | $tax_obj = get_taxonomy($tax); 87 | $tax_terms[] = $tax_obj->labels->singular_name . ': ' . implode(', ', $term_names); 88 | } 89 | } 90 | 91 | $meta = " ({$post_type}"; 92 | if (!empty($tax_terms)) { 93 | $meta .= " | " . implode(' | ', $tax_terms); 94 | } 95 | $meta .= ")"; 96 | 97 | // For real usage, use a robust HTML->MD converter. This is a simplistic approach: 98 | $content_md = wp_strip_all_tags( $content ); 99 | $lines[] = "## {$title}{$meta}\n"; 100 | $lines[] = $content_md; 101 | $lines[] = ''; 102 | } 103 | 104 | $output = implode( "\n", $lines ); 105 | set_transient( $cache_key, $output, HOUR_IN_SECONDS ); 106 | return $output; 107 | } 108 | 109 | /** 110 | * Return a more compressed version (like excerpts). 111 | */ 112 | public static function get_small_text() { 113 | $cache_key = self::CACHE_KEY_PREFIX . 'small'; 114 | $cached = get_transient( $cache_key ); 115 | if ( false !== $cached ) { 116 | return $cached; 117 | } 118 | 119 | $lines = []; 120 | $lines[] = '# LLMS Small Content (Excerpts)'; 121 | $lines[] = ''; 122 | 123 | $post_ids = self::fetch_all_included_posts(); 124 | foreach ( $post_ids as $pid ) { 125 | $title = get_the_title( $pid ); 126 | $post_type = get_post_type_object(get_post_type($pid))->labels->singular_name; 127 | 128 | // Get taxonomies and terms 129 | $taxonomies = get_object_taxonomies($pid); 130 | $tax_terms = []; 131 | foreach ($taxonomies as $tax) { 132 | $terms = wp_get_post_terms($pid, $tax); 133 | if (!empty($terms) && !is_wp_error($terms)) { 134 | $term_names = wp_list_pluck($terms, 'name'); 135 | $tax_obj = get_taxonomy($tax); 136 | $tax_terms[] = $tax_obj->labels->singular_name . ': ' . implode(', ', $term_names); 137 | } 138 | } 139 | 140 | $meta = " ({$post_type}"; 141 | if (!empty($tax_terms)) { 142 | $meta .= " | " . implode(' | ', $tax_terms); 143 | } 144 | $meta .= ")"; 145 | 146 | // Use excerpt if set, otherwise generate 147 | $excerpt = get_the_excerpt( $pid ); 148 | if ( ! $excerpt ) { 149 | $excerpt = wp_trim_words( get_post( $pid )->post_content, 40 ); 150 | } 151 | $link = get_permalink( $pid ); 152 | 153 | $lines[] = "## {$title}{$meta}"; 154 | $lines[] = $excerpt; 155 | $lines[] = "Link: {$link}\n"; 156 | } 157 | 158 | $output = implode( "\n", $lines ); 159 | set_transient( $cache_key, $output, HOUR_IN_SECONDS ); 160 | return $output; 161 | } 162 | 163 | /** 164 | * Internal method to fetch all post IDs that the user wants to include, 165 | * as set in plugin settings. 166 | */ 167 | private static function fetch_all_included_posts() { 168 | $opts = get_option( LLMS_Txt_Admin::OPTION_KEY, [] ); 169 | $post_types = isset($opts['include_post_types']) && is_array($opts['include_post_types']) 170 | ? $opts['include_post_types'] 171 | : [ 'post', 'page' ]; 172 | 173 | $args = [ 174 | 'post_type' => $post_types, 175 | 'post_status' => 'publish', 176 | 'posts_per_page' => -1, 177 | 'fields' => 'ids', 178 | ]; 179 | return get_posts( $args ); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /plugins/llms-txt/includes/class-llms-txt-plugin.php: -------------------------------------------------------------------------------- 1 | index.php?llms_txt=listing 24 | add_rewrite_rule( '^llms\.txt$', 'index.php?llms_txt=listing', 'top' ); 25 | add_rewrite_rule( '^llms-full\.txt$', 'index.php?llms_txt=full', 'top' ); 26 | add_rewrite_rule( '^llms-small\.txt$', 'index.php?llms_txt=small', 'top' ); 27 | } 28 | 29 | /** 30 | * Ensure 'llms_txt' is recognized as a valid query var. 31 | */ 32 | public function add_query_var( $vars ) { 33 | $vars[] = self::QUERY_VAR; 34 | return $vars; 35 | } 36 | 37 | /** 38 | * Check if current request is for one of our special endpoints, and serve if so. 39 | */ 40 | public function maybe_serve_endpoints() { 41 | $endpoint = get_query_var( self::QUERY_VAR ); 42 | if ( empty( $endpoint ) ) { 43 | return; // not ours 44 | } 45 | // We have 'listing', 'full', or 'small' 46 | switch ( $endpoint ) { 47 | case 'listing': 48 | $output = LLMS_Txt_Content::get_listing_text(); 49 | $this->send_as_text( $output ); 50 | break; 51 | case 'full': 52 | $output = LLMS_Txt_Content::get_full_text(); 53 | $this->send_as_text( $output ); 54 | break; 55 | case 'small': 56 | $output = LLMS_Txt_Content::get_small_text(); 57 | $this->send_as_text( $output ); 58 | break; 59 | default: 60 | // Not recognized — do nothing 61 | break; 62 | } 63 | } 64 | 65 | /** 66 | * Helper to send plain text response and exit. 67 | */ 68 | private function send_as_text( $content ) { 69 | header( 'Content-Type: text/plain; charset=utf-8' ); 70 | echo $content; 71 | exit; // Stop WP 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /plugins/llms-txt/llms-txt-plugin.php: -------------------------------------------------------------------------------- 1 |