loading…
Search for a command to run...
loading…
A Model Context Protocol server that lets Claude manage keyCRM catalogue, stock, orders, customers, pipelines, and more via natural language.
A Model Context Protocol server that lets Claude manage keyCRM catalogue, stock, orders, customers, pipelines, and more via natural language.
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
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:
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-mcp
npm install
cp .env.example .env
Edit .env with your values — see Section 4.
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.
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.js
Then set the environment variable:
claude mcp env set keycrm KEYCRM_API_KEY your_api_key_here
If 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 startup
list_productsList 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_productGet 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_productCreate 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_productUpdate 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_productChange 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_productChange 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_productArchive 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_photoReplace 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_productsApply 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:
dry_run: true → returns list of affected productsdry_run: false, confirm: true → executes the updateIn keyCRM, product variants (combinations of size, color, etc.) are called offers. Each offer has its own SKU, price, and stock level.
list_offersList 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_offerCreate 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_offerUpdate 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_categoriesList 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_categoryCreate a new product category.
Input parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
name |
string | ✅ | Category name |
parent_id |
number | ❌ | Parent category ID for nested categories |
get_stockGet 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_stockManually 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_ordersList 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_orderGet 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_orderCreate 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_orderUpdate 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_statusUpdate 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_paymentRecord 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_tagAttach 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_tagRemove 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_order_statusesList all available order statuses with their IDs and names.
Input parameters: None
list_payment_methodsList all available payment methods with their IDs and names.
Input parameters: None
list_sourcesList all available order sources (e.g. WooCommerce, POS, Telegram) with their IDs and names.
Input parameters: None
list_tagsList all available order tags with their IDs and names.
Input parameters: None
list_delivery_servicesList all available delivery services with their IDs and names.
Input parameters: None
list_external_transactionsList 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_transactionAttach 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_customersList 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_customerGet a customer profile including full order history.
Input parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
customer_id |
number | ✅ | keyCRM customer ID |
create_customerCreate 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_customerUpdate 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_customersBulk 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_pipelinesList all pipelines with their IDs and names.
Input parameters: None
list_pipeline_statusesList all stages for a specific pipeline with their IDs and names.
Input parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
pipeline_id |
number | ✅ | Pipeline ID |
list_pipeline_cardsList 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_cardGet 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_cardCreate 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_cardUpdate 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_fileUpload 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_filesList 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_custom_fieldsList all custom fields configured in keyCRM with their IDs, names, types, and allowed values.
Input parameters: None
list_warehousesList 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:
KEYCRM_RATE_LIMIT error to ClaudeAll 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/sdk
Tools 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:
Authorization: Bearer YOUR_KEY)update_product_photo — Storage API flowThis tool performs two sequential API calls internally:
POST /storage/upload — uploads the image from the provided URL to keyCRM Storage, returns a file_idPUT /products/{productId} — attaches the file_id to the product, optionally replacing existing photosIf 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 flowTwo-phase call on the same tool:
dry_run: true): Fetches matching products using the provided filter, returns the list without modifying anythingdry_run: false, confirm: true): Executes the update on all matching productsIf 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:
src/tools/)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
Выполни в терминале:
claude mcp add keycrm-mcp -- npx Безопасность
Низкий рискАвтоматическая эвристика по публичным данным — не гарантия безопасности.