keycrm-mcp
by IvanKlymenko
README.md
# keycrm-mcp
> A Model Context Protocol (MCP) server for [keyCRM](https://keycrm.app) — lets Claude manage your keyCRM catalogue, stock, orders, customers, pipelines, and more via natural language.
**Author:** Ivan Klymenko
**License:** MIT
**Node.js:** 23.6+
**MCP SDK:** `@modelcontextprotocol/sdk`
---
## Table of Contents
1. [What This Is](#1-what-this-is)
2. [Requirements](#2-requirements)
3. [Installation](#3-installation)
4. [Configuration](#4-configuration)
5. [MCP Client Setup](#5-mcp-client-setup)
6. [Tool Reference](#6-tool-reference)
- 6.1 [Products](#61-products)
- 6.2 [Product Variants (Offers)](#62-product-variants-offers)
- 6.3 [Product Categories](#63-product-categories)
- 6.4 [Stock](#64-stock)
- 6.5 [Orders](#65-orders)
- 6.6 [Order Reference Data](#66-order-reference-data)
- 6.7 [Payments](#67-payments)
- 6.8 [Customers](#68-customers)
- 6.9 [Pipelines](#69-pipelines)
- 6.10 [Storage](#610-storage)
- 6.11 [Custom Fields](#611-custom-fields)
- 6.12 [Warehouses](#612-warehouses)
7. [Error Handling](#7-error-handling)
8. [Logging](#8-logging)
9. [Project Structure](#9-project-structure)
10. [Implementation Notes](#10-implementation-notes)
11. [Contributing](#11-contributing)
---
## 1. What This Is
`keycrm-mcp` is a [Model Context Protocol](https://modelcontextprotocol.io) server that wraps the [keyCRM REST API](https://docs.keycrm.app). It exposes Claude-callable tools for managing products, variants, stock, orders, customers, pipelines, payments, files, and more in keyCRM.
Once configured, you can ask Claude things like:
- *"Show me all draft products in the Shirts category"*
- *"Update the price of SKU MNL-SRT-WHT-L to 890"*
- *"What is the current stock level for SKU ABC-123?"*
- *"List all orders placed today that are still pending"*
- *"Publish all draft products in the Jackets category"* (with preview + confirmation)
- *"Replace the photo on product ID 42 with this image URL"*
- *"Create a new order for buyer 99 with two items"*
- *"Show me all cards in the Sales pipeline"*
The server runs as a local Node.js process and communicates with your MCP client (Claude Desktop or Claude Code) over stdio.
---
## 2. Requirements
| Requirement | Version |
|---|---|
| Node.js | 23.6+ |
| npm | 10+ |
| keyCRM account | Any plan |
| keyCRM API key | Required |
---
## 3. Installation
### 3.1 Clone the repository
```bash
git clone https://github.com/ivanklymenko/keycrm-mcp.git
cd keycrm-mcp
```
### 3.2 Install dependencies
```bash
npm install
```
### 3.3 Create environment file
```bash
cp .env.example .env
```
Edit `.env` with your values — see [Section 4](#4-configuration).
### 3.4 Verify the server starts
```bash
node index.js
```
The server starts in stdio mode and waits for MCP client input. No output means it is working correctly — it only speaks when addressed by a client.
---
## 4. Configuration
All configuration is done via environment variables. Copy `.env.example` to `.env` and fill in the values.
### .env.example
```env
# ─── Required ────────────────────────────────────────────────
# Your keyCRM API key
# Found at: Налаштування → Інтеграції → API
KEYCRM_API_KEY=your_api_key_here
# ─── Optional ────────────────────────────────────────────────
# keyCRM API base URL — only change if keyCRM updates their API endpoint
KEYCRM_API_URL=https://openapi.keycrm.app/v1
# Maximum number of results returned by list tools (default: 50)
LIST_DEFAULT_LIMIT=50
# Log level: error | warn | info | debug (default: info)
LOG_LEVEL=info
# Path to the log file (default: ./logs/keycrm-mcp.log)
LOG_FILE=./logs/keycrm-mcp.log
```
### Configuration reference
| Variable | Required | Default | Description |
|---|---|---|---|
| `KEYCRM_API_KEY` | ✅ | — | keyCRM API key |
| `KEYCRM_API_URL` | ❌ | `https://openapi.keycrm.app/v1` | keyCRM API base URL |
| `LIST_DEFAULT_LIMIT` | ❌ | `50` | Default page size for list tools |
| `LOG_LEVEL` | ❌ | `info` | Log verbosity |
| `LOG_FILE` | ❌ | `./logs/keycrm-mcp.log` | Log file path |
---
## 5. MCP Client Setup
### Claude Desktop
Add the following to your `claude_desktop_config.json`:
```json
{
"mcpServers": {
"keycrm": {
"type": "stdio",
"command": "node",
"args": ["/absolute/path/to/keycrm-mcp/index.js"],
"env": {
"KEYCRM_API_KEY": "your_api_key_here"
}
}
}
}
```
Restart Claude Desktop after saving the config. The keyCRM tools will appear in the tool list automatically.
### Claude Code
```bash
claude mcp add keycrm node /absolute/path/to/keycrm-mcp/index.js
```
Then set the environment variable:
```bash
claude mcp env set keycrm KEYCRM_API_KEY your_api_key_here
```
### Running with PM2 (server / VPS)
If you are running the MCP server on a VPS and connecting remotely:
```bash
npm install -g pm2
pm2 start index.js --name keycrm-mcp
pm2 save
pm2 startup
```
---
## 6. Tool Reference
### 6.1 Products
---
#### `list_products`
List products from the keyCRM catalogue with optional filters.
**Input parameters:**
| Parameter | Type | Required | Description |
|---|---|---|---|
| `category_id` | number | ❌ | Filter by category ID |
| `status` | string | ❌ | Filter by status: `draft`, `published`, `archived` |
| `query` | string | ❌ | Search by product name |
| `limit` | number | ❌ | Number of results to return (default: `LIST_DEFAULT_LIMIT`) |
| `offset` | number | ❌ | Pagination offset (default: 0) |
---
#### `get_product`
Get full details for a single product including all variants and stock levels per warehouse.
**Input parameters:**
| Parameter | Type | Required | Description |
|---|---|---|---|
| `product_id` | number | ✅ | keyCRM product ID |
---
#### `create_product`
Create a new product in the keyCRM catalogue.
**Input parameters:**
| Parameter | Type | Required | Description |
|---|---|---|---|
| `name` | string | ✅ | Product name |
| `category_id` | number | ❌ | Category ID |
| `description` | string | ❌ | Product description |
| `price` | number | ❌ | Base price |
| `sku` | string | ❌ | Product SKU |
| `status` | string | ❌ | Initial status: `draft` (default) or `published` |
---
#### `update_product`
Update one or more fields on an existing product. Does not change product status — use `publish_product`, `unpublish_product`, or `archive_product` for status changes.
**Input parameters:**
| Parameter | Type | Required | Description |
|---|---|---|---|
| `product_id` | number | ✅ | keyCRM product ID |
| `name` | string | ❌ | Product name |
| `description` | string | ❌ | Product description |
| `price` | number | ❌ | Product price |
| `category_id` | number | ❌ | Category ID |
| `sku` | string | ❌ | Product SKU |
At least one optional field must be provided.
---
#### `publish_product`
Change a product's status from `draft` to `published`. Makes the product visible on the storefront.
**Input parameters:**
| Parameter | Type | Required | Description |
|---|---|---|---|
| `product_id` | number | ✅ | keyCRM product ID |
---
#### `unpublish_product`
Change a product's status from `published` back to `draft`. Hides the product from the storefront without deleting it.
**Input parameters:**
| Parameter | Type | Required | Description |
|---|---|---|---|
| `product_id` | number | ✅ | keyCRM product ID |
---
#### `archive_product`
Archive a product. Archived products are hidden from the storefront but fully preserved in keyCRM and can be unarchived at any time. No confirmation required — this operation is reversible.
**Input parameters:**
| Parameter | Type | Required | Description |
|---|---|---|---|
| `product_id` | number | ✅ | keyCRM product ID |
---
#### `update_product_photo`
Replace or add a photo on an existing product. Accepts a publicly accessible image URL. Internally, the file is uploaded to keyCRM Storage first (`POST /storage/upload`), then attached to the product — this is handled automatically by the server.
**Input parameters:**
| Parameter | Type | Required | Description |
|---|---|---|---|
| `product_id` | number | ✅ | keyCRM product ID |
| `photo_url` | string | ✅ | Publicly accessible URL of the new photo |
| `replace_existing` | boolean | ❌ | If `true`, replaces all existing photos. If `false`, adds alongside existing ones (default: `false`) |
---
#### `bulk_update_products`
Apply a field update to multiple products matching a filter. Always call with `dry_run: true` first to preview affected products, then call again with `dry_run: false` and `confirm: true` to execute.
**Input parameters:**
| Parameter | Type | Required | Description |
|---|---|---|---|
| `filter` | object | ✅ | Filter defining which products to update |
| `filter.category_id` | number | ❌ | Match products in this category |
| `filter.status` | string | ❌ | Match products with this status |
| `filter.query` | string | ❌ | Match products whose name contains this string |
| `update` | object | ✅ | Fields to update and their new values (same fields as `update_product`) |
| `dry_run` | boolean | ✅ | If `true`, returns preview without making changes |
| `confirm` | boolean | ❌ | Must be `true` to execute when `dry_run` is `false` |
**Two-step flow:**
1. Call with `dry_run: true` → returns list of affected products
2. Review the list, then call again with `dry_run: false, confirm: true` → executes the update
---
### 6.2 Product Variants (Offers)
In keyCRM, product variants (combinations of size, color, etc.) are called **offers**. Each offer has its own SKU, price, and stock level.
---
#### `list_offers`
List product variants with optional filters.
**Input parameters:**
| Parameter | Type | Required | Description |
|---|---|---|---|
| `product_id` | number | ❌ | Filter offers by parent product ID |
| `sku` | string | ❌ | Filter by SKU (partial match) |
| `limit` | number | ❌ | Number of results to return (default: `LIST_DEFAULT_LIMIT`) |
| `offset` | number | ❌ | Pagination offset (default: 0) |
---
#### `create_offer`
Create one or more new variants for an existing product.
**Input parameters:**
| Parameter | Type | Required | Description |
|---|---|---|---|
| `product_id` | number | ✅ | Parent product ID |
| `offers` | array | ✅ | Array of offer objects to create |
| `offers[].sku` | string | ❌ | Variant SKU |
| `offers[].price` | number | ❌ | Variant price |
| `offers[].properties` | array | ❌ | Array of `{name, value}` pairs (e.g. `{name: "Розмір", value: "M"}`) |
---
#### `update_offer`
Update fields on one or more existing product variants.
**Input parameters:**
| Parameter | Type | Required | Description |
|---|---|---|---|
| `offers` | array | ✅ | Array of offer update objects |
| `offers[].id` | number | ✅ | Offer ID |
| `offers[].sku` | string | ❌ | New SKU |
| `offers[].price` | number | ❌ | New price |
| `offers[].properties` | array | ❌ | Updated properties |
---
### 6.3 Product Categories
---
#### `list_categories`
List all product categories.
**Input parameters:**
| Parameter | Type | Required | Description |
|---|---|---|---|
| `limit` | number | ❌ | Number of results to return (default: `LIST_DEFAULT_LIMIT`) |
| `offset` | number | ❌ | Pagination offset (default: 0) |
---
#### `create_category`
Create a new product category.
**Input parameters:**
| Parameter | Type | Required | Description |
|---|---|---|---|
| `name` | string | ✅ | Category name |
| `parent_id` | number | ❌ | Parent category ID for nested categories |
---
### 6.4 Stock
---
#### `get_stock`
Get stock levels for a specific SKU across all warehouses, or for a single warehouse.
**Input parameters:**
| Parameter | Type | Required | Description |
|---|---|---|---|
| `sku` | string | ✅ | Product variant SKU |
| `warehouse_id` | number | ❌ | If provided, returns stock for this warehouse only |
---
#### `adjust_stock`
Manually adjust the stock level for a SKU in a specific warehouse. Sets an absolute quantity — not a delta.
**Input parameters:**
| Parameter | Type | Required | Description |
|---|---|---|---|
| `sku` | string | ✅ | Product variant SKU |
| `warehouse_id` | number | ✅ | Warehouse to adjust stock in |
| `quantity` | number | ✅ | New absolute stock quantity |
| `reason` | string | ❌ | Optional note explaining the adjustment |
---
### 6.5 Orders
---
#### `list_orders`
List orders with optional filters.
**Input parameters:**
| Parameter | Type | Required | Description |
|---|---|---|---|
| `status_id` | number | ❌ | Filter by order status ID (use `list_order_statuses` to get IDs) |
| `date_from` | string | ❌ | Filter orders created from this date (ISO 8601: `YYYY-MM-DD`) |
| `date_to` | string | ❌ | Filter orders created up to this date (ISO 8601: `YYYY-MM-DD`) |
| `source_id` | number | ❌ | Filter by source ID (use `list_sources` to get IDs) |
| `warehouse_id` | number | ❌ | Filter by fulfillment warehouse |
| `limit` | number | ❌ | Number of results to return (default: `LIST_DEFAULT_LIMIT`) |
| `offset` | number | ❌ | Pagination offset (default: 0) |
---
#### `get_order`
Get full details for a single order including line items, customer, payments, tags, and status history.
**Input parameters:**
| Parameter | Type | Required | Description |
|---|---|---|---|
| `order_id` | number | ✅ | keyCRM order ID |
---
#### `create_order`
Create a new order in keyCRM. Requires explicit confirmation.
**Input parameters:**
| Parameter | Type | Required | Description |
|---|---|---|---|
| `buyer_id` | number | ❌ | Existing buyer ID |
| `buyer_comment` | string | ❌ | Comment from the buyer |
| `manager_comment` | string | ❌ | Internal manager comment |
| `source_id` | number | ❌ | Source ID (use `list_sources` to get IDs) |
| `status_id` | number | ❌ | Initial status ID (use `list_order_statuses` to get IDs) |
| `payment_method_id` | number | ❌ | Payment method ID (use `list_payment_methods` to get IDs) |
| `warehouse_id` | number | ❌ | Fulfillment warehouse ID |
| `products` | array | ✅ | Array of line item objects |
| `products[].offer_id` | number | ✅ | Offer (variant) ID |
| `products[].quantity` | number | ✅ | Quantity |
| `products[].price` | number | ❌ | Sale price (overrides catalogue price) |
| `shipping` | object | ❌ | Shipping details (delivery service, address, TTN, etc.) |
| `confirm` | boolean | ✅ | Must be `true` to execute |
---
#### `update_order`
Update fields on an existing order.
**Input parameters:**
| Parameter | Type | Required | Description |
|---|---|---|---|
| `order_id` | number | ✅ | keyCRM order ID |
| `manager_comment` | string | ❌ | Internal manager comment |
| `buyer_comment` | string | ❌ | Buyer comment |
| `shipping` | object | ❌ | Updated shipping details |
| `payment_method_id` | number | ❌ | Payment method ID |
At least one optional field must be provided.
---
#### `update_order_status`
Update the status of an order.
**Input parameters:**
| Parameter | Type | Required | Description |
|---|---|---|---|
| `order_id` | number | ✅ | keyCRM order ID |
| `status_id` | number | ✅ | New status ID (use `list_order_statuses` to get IDs) |
| `note` | string | ❌ | Optional internal note |
---
#### `add_order_payment`
Record a payment against an existing order.
**Input parameters:**
| Parameter | Type | Required | Description |
|---|---|---|---|
| `order_id` | number | ✅ | keyCRM order ID |
| `amount` | number | ✅ | Payment amount |
| `payment_method_id` | number | ❌ | Payment method ID (use `list_payment_methods` to get IDs) |
| `description` | string | ❌ | Optional payment note |
---
#### `add_order_tag`
Attach a tag to an existing order.
**Input parameters:**
| Parameter | Type | Required | Description |
|---|---|---|---|
| `order_id` | number | ✅ | keyCRM order ID |
| `tag_id` | number | ✅ | Tag ID (use `list_tags` to get IDs) |
---
#### `remove_order_tag`
Remove a tag from an existing order.
**Input parameters:**
| Parameter | Type | Required | Description |
|---|---|---|---|
| `order_id` | number | ✅ | keyCRM order ID |
| `tag_id` | number | ✅ | Tag ID |
---
### 6.6 Order Reference Data
These tools return the lookup data needed to build valid order requests. Call them to get correct IDs before creating or updating orders.
---
#### `list_order_statuses`
List all available order statuses with their IDs and names.
**Input parameters:** None
---
#### `list_payment_methods`
List all available payment methods with their IDs and names.
**Input parameters:** None
---
#### `list_sources`
List all available order sources (e.g. WooCommerce, POS, Telegram) with their IDs and names.
**Input parameters:** None
---
#### `list_tags`
List all available order tags with their IDs and names.
**Input parameters:** None
---
#### `list_delivery_services`
List all available delivery services with their IDs and names.
**Input parameters:** None
---
### 6.7 Payments
---
#### `list_external_transactions`
List external payment transactions recorded in keyCRM (e.g. from Monobank or other payment providers).
**Input parameters:**
| Parameter | Type | Required | Description |
|---|---|---|---|
| `limit` | number | ❌ | Number of results to return (default: `LIST_DEFAULT_LIMIT`) |
| `offset` | number | ❌ | Pagination offset (default: 0) |
---
#### `attach_external_transaction`
Attach an external transaction to an existing payment record in keyCRM. Used to link a bank transaction to a keyCRM order payment.
**Input parameters:**
| Parameter | Type | Required | Description |
|---|---|---|---|
| `payment_id` | number | ✅ | keyCRM payment ID |
| `transaction_id` | string | ✅ | External transaction identifier (e.g. from Monobank) |
| `amount` | number | ✅ | Transaction amount |
| `description` | string | ❌ | Optional description |
---
### 6.8 Customers
---
#### `list_customers`
List customers with optional search.
**Input parameters:**
| Parameter | Type | Required | Description |
|---|---|---|---|
| `query` | string | ❌ | Search by name, email, or phone |
| `limit` | number | ❌ | Number of results to return (default: `LIST_DEFAULT_LIMIT`) |
| `offset` | number | ❌ | Pagination offset (default: 0) |
---
#### `get_customer`
Get a customer profile including full order history.
**Input parameters:**
| Parameter | Type | Required | Description |
|---|---|---|---|
| `customer_id` | number | ✅ | keyCRM customer ID |
---
#### `create_customer`
Create a new customer (buyer) record in keyCRM.
**Input parameters:**
| Parameter | Type | Required | Description |
|---|---|---|---|
| `full_name` | string | ✅ | Customer full name |
| `email` | string | ❌ | Email address |
| `phone` | string | ❌ | Phone number |
| `comment` | string | ❌ | Internal note |
---
#### `update_customer`
Update an existing customer record.
**Input parameters:**
| Parameter | Type | Required | Description |
|---|---|---|---|
| `customer_id` | number | ✅ | keyCRM customer ID |
| `full_name` | string | ❌ | Customer full name |
| `email` | string | ❌ | Email address |
| `phone` | string | ❌ | Phone number |
| `comment` | string | ❌ | Internal note |
At least one optional field must be provided.
---
#### `import_customers`
Bulk import a list of customer records into keyCRM. Requires explicit confirmation.
**Input parameters:**
| Parameter | Type | Required | Description |
|---|---|---|---|
| `customers` | array | ✅ | Array of customer objects |
| `customers[].full_name` | string | ✅ | Customer full name |
| `customers[].email` | string | ❌ | Email address |
| `customers[].phone` | string | ❌ | Phone number |
| `confirm` | boolean | ✅ | Must be `true` to execute |
---
### 6.9 Pipelines
Pipelines are keyCRM's sales funnel and lead management feature. Each pipeline contains cards (leads or deals) that move through defined stages.
---
#### `list_pipelines`
List all pipelines with their IDs and names.
**Input parameters:** None
---
#### `list_pipeline_statuses`
List all stages for a specific pipeline with their IDs and names.
**Input parameters:**
| Parameter | Type | Required | Description |
|---|---|---|---|
| `pipeline_id` | number | ✅ | Pipeline ID |
---
#### `list_pipeline_cards`
List cards across pipelines with optional filters.
**Input parameters:**
| Parameter | Type | Required | Description |
|---|---|---|---|
| `pipeline_id` | number | ❌ | Filter by pipeline ID |
| `status_id` | number | ❌ | Filter by pipeline stage ID |
| `limit` | number | ❌ | Number of results to return (default: `LIST_DEFAULT_LIMIT`) |
| `offset` | number | ❌ | Pagination offset (default: 0) |
---
#### `get_pipeline_card`
Get full details for a single pipeline card including contact, products, payments, and status.
**Input parameters:**
| Parameter | Type | Required | Description |
|---|---|---|---|
| `card_id` | number | ✅ | Pipeline card ID |
---
#### `create_pipeline_card`
Create a new card in a pipeline.
**Input parameters:**
| Parameter | Type | Required | Description |
|---|---|---|---|
| `pipeline_id` | number | ✅ | Pipeline ID |
| `status_id` | number | ✅ | Initial stage ID (use `list_pipeline_statuses` to get IDs) |
| `title` | string | ❌ | Card title |
| `contact` | object | ❌ | Contact details (`full_name`, `email`, `phone`) |
| `products` | array | ❌ | Array of product line items |
| `manager_comment` | string | ❌ | Internal note |
---
#### `update_pipeline_card`
Update an existing pipeline card (move stage, update contact, add notes, etc.).
**Input parameters:**
| Parameter | Type | Required | Description |
|---|---|---|---|
| `card_id` | number | ✅ | Pipeline card ID |
| `status_id` | number | ❌ | New stage ID |
| `title` | string | ❌ | Updated title |
| `manager_comment` | string | ❌ | Updated internal note |
| `contact` | object | ❌ | Updated contact details (`full_name`, `email`, `phone`) |
At least one optional field must be provided.
---
### 6.10 Storage
---
#### `upload_file`
Upload a file to keyCRM Storage from a publicly accessible URL. Returns a `file_id` that can be used to attach the file to orders, pipeline cards, or products.
**Input parameters:**
| Parameter | Type | Required | Description |
|---|---|---|---|
| `url` | string | ✅ | Publicly accessible URL of the file to upload |
| `filename` | string | ❌ | Optional filename override |
---
#### `list_files`
List files stored in keyCRM Storage, optionally filtered by the entity they are attached to.
**Input parameters:**
| Parameter | Type | Required | Description |
|---|---|---|---|
| `entity_type` | string | ❌ | Filter by entity type: `order`, `pipelines_card`, `product` |
| `entity_id` | number | ❌ | Filter by entity ID (requires `entity_type`) |
| `limit` | number | ❌ | Number of results to return (default: `LIST_DEFAULT_LIMIT`) |
| `offset` | number | ❌ | Pagination offset (default: 0) |
---
### 6.11 Custom Fields
---
#### `list_custom_fields`
List all custom fields configured in keyCRM with their IDs, names, types, and allowed values.
**Input parameters:** None
---
### 6.12 Warehouses
---
#### `list_warehouses`
List all warehouses configured in keyCRM with their IDs, names, and addresses.
**Input parameters:** None
---
## 7. Error Handling
All keyCRM API errors are caught and returned to Claude as structured error messages — they are never thrown as unhandled exceptions.
### Error response shape
```json
{
"error": true,
"code": "KEYCRM_API_ERROR",
"status": 404,
"message": "Product not found",
"detail": "No product with ID 9999 exists in keyCRM"
}
```
### Error codes
| Code | Description |
|---|---|
| `KEYCRM_API_ERROR` | keyCRM returned a non-2xx response |
| `KEYCRM_AUTH_ERROR` | API key is invalid or missing |
| `KEYCRM_RATE_LIMIT` | Rate limit hit — request will be retried |
| `KEYCRM_TIMEOUT` | Request timed out |
| `VALIDATION_ERROR` | Tool input failed validation before the API was called |
| `INTERNAL_ERROR` | Unexpected server error |
### Rate limiting
The keyCRM API enforces a limit of **60 requests per minute per IP address per API key**. The server handles HTTP 429 responses automatically with exponential backoff:
- First retry: 1 second
- Second retry: 2 seconds
- Third retry: 4 seconds
- After 3 retries: returns a `KEYCRM_RATE_LIMIT` error to Claude
### Timezone
All timestamps in the keyCRM API use **UTC (GMT+0)** — for reads, filters, and writes. The server does not perform timezone conversion. Pass and expect UTC values in all date/time fields.
---
## 8. Logging
All tool calls and API interactions are logged to a local file for debugging.
### Log format
```
[2026-03-25T14:32:01.123Z] [INFO] tool_call: list_products | params: {"status":"draft"} | duration: 312ms | status: ok
[2026-03-25T14:32:05.456Z] [ERROR] tool_call: get_product | params: {"product_id":9999} | duration: 201ms | status: error | code: KEYCRM_API_ERROR | message: Product not found
```
### Log location
Default: `./logs/keycrm-mcp.log`
Override with the `LOG_FILE` environment variable.
Logs are appended — no automatic rotation is implemented. Use `logrotate` on Linux/VPS deployments or clear manually as needed.
---
## 9. Project Structure
```
keycrm-mcp/
├── index.js # Entry point — starts the MCP server
├── .env.example # Environment variable template
├── .env # Your local config (not committed)
├── package.json
├── src/
│ ├── server.js # MCP server setup and tool registration
│ ├── tools/
│ │ ├── products.js # list_products, get_product, create_product,
│ │ │ # update_product, publish_product, unpublish_product,
│ │ │ # archive_product, update_product_photo,
│ │ │ # bulk_update_products
│ │ ├── offers.js # list_offers, create_offer, update_offer
│ │ ├── categories.js # list_categories, create_category
│ │ ├── stock.js # get_stock, adjust_stock
│ │ ├── orders.js # list_orders, get_order, create_order,
│ │ │ # update_order, update_order_status,
│ │ │ # add_order_payment, add_order_tag,
│ │ │ # remove_order_tag
│ │ ├── order-reference.js # list_order_statuses, list_payment_methods,
│ │ │ # list_sources, list_tags, list_delivery_services
│ │ ├── payments.js # list_external_transactions,
│ │ │ # attach_external_transaction
│ │ ├── customers.js # list_customers, get_customer, create_customer,
│ │ │ # update_customer, import_customers
│ │ ├── pipelines.js # list_pipelines, list_pipeline_statuses,
│ │ │ # list_pipeline_cards, get_pipeline_card,
│ │ │ # create_pipeline_card, update_pipeline_card
│ │ ├── storage.js # upload_file, list_files
│ │ ├── custom-fields.js # list_custom_fields
│ │ └── warehouses.js # list_warehouses
│ ├── keycrm/
│ │ ├── client.js # keyCRM REST API client (fetch wrapper, auth, retry)
│ │ └── errors.js # Error normalisation
│ └── utils/
│ ├── logger.js # File logger
│ └── validate.js # Input validation helpers
└── logs/
└── keycrm-mcp.log # Runtime log (auto-created)
```
---
## 10. Implementation Notes
### MCP SDK
This server is built with the official Anthropic MCP SDK:
```bash
npm install @modelcontextprotocol/sdk
```
Tools are registered using the SDK's `server.tool()` method. Input schemas are defined using [Zod](https://zod.dev) for runtime validation.
### Transport
The server uses `StdioServerTransport` from the MCP SDK:
```js
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
const server = new McpServer({ name: 'keycrm-mcp', version: '1.0.0' });
const transport = new StdioServerTransport();
await server.connect(transport);
```
### keyCRM API client
All keyCRM API calls go through a single client module (`src/keycrm/client.js`) that handles:
- Base URL and API key injection from environment variables
- Bearer token authentication (`Authorization: Bearer YOUR_KEY`)
- JSON request/response serialisation
- HTTP error normalisation
- Rate limit retry with exponential backoff (max 3 retries)
- Request timeout (default: 10 seconds)
### `update_product_photo` — Storage API flow
This tool performs two sequential API calls internally:
1. `POST /storage/upload` — uploads the image from the provided URL to keyCRM Storage, returns a `file_id`
2. `PUT /products/{productId}` — attaches the `file_id` to the product, optionally replacing existing photos
If the upload succeeds but the attach fails, the error is returned with the `file_id` included so the attach can be retried manually if needed.
### `bulk_update_products` dry-run flow
Two-phase call on the same tool:
1. **Phase 1** (`dry_run: true`): Fetches matching products using the provided filter, returns the list without modifying anything
2. **Phase 2** (`dry_run: false, confirm: true`): Executes the update on all matching products
If `dry_run: false` is passed without `confirm: true`, the tool returns a `VALIDATION_ERROR` — it will not execute without explicit confirmation.
### ESM
The project uses ES modules (`"type": "module"` in `package.json`). All imports use ESM syntax.
---
## 11. Contributing
Contributions are welcome. This is a generic keyCRM MCP server — pull requests that extend coverage of the keyCRM API are encouraged.
**Before opening a PR:**
- Follow the existing file structure (one file per resource group in `src/tools/`)
- Add input parameter validation using Zod for every new tool
- Ensure errors are caught and returned as structured error objects, not thrown
- Update this README with the new tool in Section 6
**To report a bug or request a tool:** open a GitHub issue with the keyCRM API endpoint you need covered and a description of the use case.
---
*keycrm-mcp · Ivan Klymenko · MIT License*
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues