A Model Context Protocol (MCP) server for keyCRM — 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
- What This Is
- Requirements
- Installation
- Configuration
- MCP Client Setup
- Tool Reference
- 6.1 Products
- 6.2 Product Variants (Offers)
- 6.3 Product Categories
- 6.4 Stock
- 6.5 Orders
- 6.6 Order Reference Data
- 6.7 Payments
- 6.8 Customers
- 6.9 Pipelines
- 6.10 Storage
- 6.11 Custom Fields
- 6.12 Warehouses
- Error Handling
- Logging
- Project Structure
- Implementation Notes
- Contributing
keycrm-mcp is a Model Context Protocol server that wraps the keyCRM REST API. 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.
| Requirement | Version |
|---|---|
| Node.js | 23.6+ |
| npm | 10+ |
| keyCRM account | Any plan |
| keyCRM API key | Required |
git clone https://github.com/ivanklymenko/keycrm-mcp.git
cd keycrm-mcpnpm installcp .env.example .envEdit .env with your values — see Section 4.
node index.jsThe 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.
All configuration is done via environment variables. Copy .env.example to .env and fill in the values.
# ─── 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| 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 |
Add the following to your claude_desktop_config.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 mcp add keycrm node /absolute/path/to/keycrm-mcp/index.jsThen set the environment variable:
claude mcp env set keycrm KEYCRM_API_KEY your_api_key_hereIf you are running the MCP server on a VPS and connecting remotely:
npm install -g pm2
pm2 start index.js --name keycrm-mcp
pm2 save
pm2 startupList 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 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 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 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.
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 |
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 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 |
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) |
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:
- Call with
dry_run: true→ returns list of affected products - Review the list, then call again with
dry_run: false, confirm: true→ executes the update
In keyCRM, product variants (combinations of size, color, etc.) are called offers. Each offer has its own SKU, price, and stock level.
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 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 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 |
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 a new product category.
Input parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
name |
string | ✅ | Category name |
parent_id |
number | ❌ | Parent category ID for nested categories |
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 |
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 |
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 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 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 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 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 |
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 |
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 a tag from an existing order.
Input parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
order_id |
number | ✅ | keyCRM order ID |
tag_id |
number | ✅ | Tag ID |
These tools return the lookup data needed to build valid order requests. Call them to get correct IDs before creating or updating orders.
List all available order statuses with their IDs and names.
Input parameters: None
List all available payment methods with their IDs and names.
Input parameters: None
List all available order sources (e.g. WooCommerce, POS, Telegram) with their IDs and names.
Input parameters: None
List all available order tags with their IDs and names.
Input parameters: None
List all available delivery services with their IDs and names.
Input parameters: None
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 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 |
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 a customer profile including full order history.
Input parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
customer_id |
number | ✅ | keyCRM customer ID |
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 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.
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 |
Pipelines are keyCRM's sales funnel and lead management feature. Each pipeline contains cards (leads or deals) that move through defined stages.
List all pipelines with their IDs and names.
Input parameters: None
List all stages for a specific pipeline with their IDs and names.
Input parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
pipeline_id |
number | ✅ | Pipeline ID |
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 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 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 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.
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 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) |
List all custom fields configured in keyCRM with their IDs, names, types, and allowed values.
Input parameters: None
List all warehouses configured in keyCRM with their IDs, names, and addresses.
Input parameters: None
All keyCRM API errors are caught and returned to Claude as structured error messages — they are never thrown as unhandled exceptions.
{
"error": true,
"code": "KEYCRM_API_ERROR",
"status": 404,
"message": "Product not found",
"detail": "No product with ID 9999 exists in keyCRM"
}| 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 |
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_LIMITerror to Claude
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.
All tool calls and API interactions are logged to a local file for debugging.
[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
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.
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)
This server is built with the official Anthropic MCP SDK:
npm install @modelcontextprotocol/sdkTools are registered using the SDK's server.tool() method. Input schemas are defined using Zod for runtime validation.
The server uses StdioServerTransport from the MCP SDK:
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);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)
This tool performs two sequential API calls internally:
POST /storage/upload— uploads the image from the provided URL to keyCRM Storage, returns afile_idPUT /products/{productId}— attaches thefile_idto 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.
Two-phase call on the same tool:
- Phase 1 (
dry_run: true): Fetches matching products using the provided filter, returns the list without modifying anything - 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.
The project uses ES modules ("type": "module" in package.json). All imports use ESM syntax.
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