Bitrix24 MCP Server
A local MCP server that lets Claude Desktop read and write a company's Bitrix24 CRM, tasks, telephony, and website chats over an inbound webhook.
The company is fictional. This server was built for, and used every day at, a real print and display shop. Here it serves Meridian Display Co., the invented company behind meridian-ops-board: this is the CRM side of the same system. Funnel names, field labels and examples are Meridian's; the engineering is the real server's.
This server performs writes. Lead writes, funnel-locked deal writes (including
create_deal), and contact/company create/update are enabled by default. SetBITRIX_WRITES=offfor a fully read-only server.
Deployment model
Local, per-machine, stdio: Claude Desktop launches server.py as a subprocess
on each staff member's machine. There is no network listener, so the
security boundary is the webhook secret in .env and the scopes you grant
it. See "Security & opsec" below before distributing.
What it does
30 tools. Reads are unrestricted; writes are gated by BITRIX_WRITES (and deal
writes are additionally locked to specific funnels).
Reads
| Tool | Purpose |
|---|---|
search_crm |
Find leads/contacts/companies/deals by name, email, or phone |
get_record |
Full detail on a record, incl. custom fields + an order summary |
get_timeline |
Activity history (calls, emails, comments) + website chats |
get_chat_transcript |
Website live-chat (Open Channel) conversations on a record |
list_tasks |
Your open/overdue/completed tasks |
pipeline_summary |
Exact per-stage counts (batched), with stage names |
list_records |
List records by assignee, stage, field, funnel, or date |
get_fields / find_field |
Field schema / search fields by label across entities |
get_products |
Line items on a record (product, qty, price, total) |
get_stage_history |
Stage movement with time-in-stage |
get_call_history |
Recent telephony calls (with CALL_IDs and recording flags) |
get_call_recording |
Recording URL/file + bound record for one call |
list_pipelines |
Deal funnels (with stages) + smart processes |
Writes (gated by BITRIX_WRITES)
| Tool | Scope of write |
|---|---|
add_lead_comment, rename_lead, set_lead_fields |
Leads |
add_deal_comment, set_deal_title, set_deal_stage, set_deal_fields, assign_deal, link_deal_contact, link_deal_company, create_deal |
Deals, write-enabled funnels only |
create_contact, update_contact, create_company, update_company |
Parties, gated also by BITRIX_WRITE_PARTIES; create dup-checks first |
transcribe_call |
Local transcription, opt-in via BITRIX_TRANSCRIBE |
set_lead_fields / set_deal_fields take a {label: value} object and
translate enum options, yes/no booleans, and dates automatically, e.g.
{"Follow-up Status": "Quote Sent", "Rush Job": "yes"}.
Webhook permissions (required)
Create one Inbound webhook (Applications → Developer resources → Other → Inbound webhook) and enable exactly these scopes:
| Scope | Powers |
|---|---|
crm |
all CRM reads + writes |
user_brief |
assignee / author names (upgrade to user if names look thin) |
task |
list_tasks |
telephony |
get_call_history, get_call_recording, transcription |
imopenlines + im |
website-chat read (commercial plan required) |
Without imopenlines/im, chat tools degrade gracefully (timeline still works).
Without telephony, the call tools return a clear "missing scope" message.
CoPilot note. Bitrix CoPilot's generated transcripts/summaries are not readable over REST, and a call's
TRANSCRIPT_IDhas no getter. "CoPilot-style" value here comes from Claude summarizing the data these tools expose (chat transcripts, recordings, timeline), not from reading CoPilot itself.
Security & opsec (read before distributing)
- The webhook URL is a password. Anyone with it has the webhook user's full
API access from anywhere (Bitrix cloud webhooks can't be IP-restricted).
- Choose a webhook-owner model and stick to it:
- Per-user (what the Windows install uses): each person's webhook lives in
their own Bitrix account, so actions are attributable to them. Never copy
one person's
.envto another machine. - Dedicated service user: one non-admin "CRM Automation" account scoped to only what's needed: simplest least-privilege, but loses per-user audit.
- Per-user (what the Windows install uses): each person's webhook lives in
their own Bitrix account, so actions are attributable to them. Never copy
one person's
- Enable 2FA on whichever account owns the webhook.
chmod 600 .envon every machine. Never commit.env(it's gitignored).- Have a rotation plan: if a machine is lost, delete + recreate the webhook
in Bitrix and update
.env.
- Choose a webhook-owner model and stick to it:
- Token never logged. httpx request logging is silenced and all error paths redact the webhook URL.
- Logs are PII-scrubbed by default.
logs/calls.jsonlrecords tool calls for migration analysis;BITRIX_LOG_ARGS=redact(default) masks emails/phones and blanks customer names/free text. Usefullonly on a trusted solo machine;offto log no arguments. The log self-rotates pastBITRIX_LOG_MAX_BYTES. - Untrusted content is fenced. Customer/visitor free text (record fields, email/chat bodies) is wrapped as untrusted data so the model treats it as data, never instructions. Keep per-write confirmation on in Claude Desktop.
Setup
cd meridian-crm-mcp
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
cp .env.example .env
# paste your webhook URL and adjust settings
chmod 600 .env # macOS/LinuxSmoke test: python server.py should log a startup line and wait silently.
Ctrl+C to stop. Then wire it into Claude Desktop's claude_desktop_config.json
under mcpServers with absolute paths to the venv python and server.py, and
restart Claude Desktop.
Checks
pip install -r requirements.txt pytest ruff
ruff check .
python -m py_compile server.py
pytest
BITRIX_WEBHOOK_URL=https://example.invalid/rest/1/x BITRIX_DATA_DIR=$(mktemp -d) \
python server.py # starts on stdio; Ctrl+C to stop
cd mcpb && ./build.sh # mcpb validate + packThe tests need no webhook and make no network calls: tests/conftest.py sets
the environment before importing server.py and swaps bitrix_call for a
recording fake, and bitrix_call itself runs against httpx.MockTransport.
tests/tool_api.json records the 30 tools' names, docstrings and parameter
schemas, so any change to what the model sees fails the suite until the
contract file is updated on purpose. CI runs lint, compile and tests on
Python 3.10 and 3.12.
Configuration
All settings live in .env (see .env.example for the annotated list):
BITRIX_WEBHOOK_URL, BITRIX_LOCAL_TZ, BITRIX_WRITES, BITRIX_WRITE_FUNNELS,
BITRIX_WRITE_PARTIES, BITRIX_LOG_ARGS, BITRIX_LOG_MAX_BYTES,
BITRIX_TRANSCRIBE.
Architecture notes
- Universal API. Reads use the
crm.item.*family (entityTypeId: lead 1, deal 2, contact 3, company 4). The one exception is party create/update, which uses legacycrm.contact.*/crm.company.*becausecrm.item.addsilently drops phone/email multifields. - Batched counts.
pipeline_summaryruns all per-stage counts in onerest.batchround-trip (chunked at 50). One sharedhttpx.AsyncClientpools connections across calls. - Semantic tool names (
search_crm, notbitrix_crm_search) so the interface survives a future CRM migration; only the implementation swaps. - Schema drift tracking. Field schemas snapshot to
./schema/(one dated file per entity per day) so form-builder changes are diffable over time. The snapshots are gitignored: they hold the live CRM's own field definitions.
Troubleshooting
BITRIX_WEBHOOK_URL not set:.envmissing/empty.insufficient_scope: add the missing scope to the webhook (see table).- Chat tools say insufficient_scope: add
imopenlines+im(commercial plan required). - A deal write is "Refused… not a write-enabled funnel": the deal isn't in
BITRIX_WRITE_FUNNELS; that's the funnel lock working as intended. - Custom field shows as
UF_CRM_…: it's a custom field;get_recordshows its label, andget_fields/find_fielddump the schema.