mattrguerra/meridian-crm-mcpmainPrivate on GitHub, published here in full

README.md

Language Markdown8.4 KB172 lines

README.md

  1. # Bitrix24 MCP Server
  2. A local MCP server that lets Claude Desktop **read and write** a company's
  3. Bitrix24 CRM, tasks, telephony, and website chats over an inbound webhook.
  4. **The company is fictional.** This server was built for, and used every day at,
  5. a real print and display shop. Here it serves **Meridian Display Co.**, the
  6. invented company behind
  7. [meridian-ops-board](https://github.com/mattrguerra/meridian-ops-board): this
  8. is the CRM side of the same system. Funnel names, field labels and examples are
  9. Meridian's; the engineering is the real server's.
  10. > **This server performs writes.** Lead writes, funnel-locked deal writes
  11. > (including `create_deal`), and contact/company create/update are **enabled by
  12. > default**. Set `BITRIX_WRITES=off` for a fully read-only server.
  13. ## Deployment model
  14. Local, per-machine, stdio: Claude Desktop launches `server.py` as a subprocess
  15. on each staff member's machine. There is **no network listener**, so the
  16. security boundary is the **webhook secret** in `.env` and the scopes you grant
  17. it. See "Security & opsec" below before distributing.
  18. ## What it does
  19. 30 tools. Reads are unrestricted; writes are gated by `BITRIX_WRITES` (and deal
  20. writes are additionally locked to specific funnels).
  21. ### Reads
  22. | Tool | Purpose |
  23. | ---- | ------- |
  24. | `search_crm` | Find leads/contacts/companies/deals by name, email, or phone |
  25. | `get_record` | Full detail on a record, incl. custom fields + an order summary |
  26. | `get_timeline` | Activity history (calls, emails, comments) + website chats |
  27. | `get_chat_transcript` | Website live-chat (Open Channel) conversations on a record |
  28. | `list_tasks` | Your open/overdue/completed tasks |
  29. | `pipeline_summary` | Exact per-stage counts (batched), with stage names |
  30. | `list_records` | List records by assignee, stage, field, funnel, or date |
  31. | `get_fields` / `find_field` | Field schema / search fields by label across entities |
  32. | `get_products` | Line items on a record (product, qty, price, total) |
  33. | `get_stage_history` | Stage movement with time-in-stage |
  34. | `get_call_history` | Recent telephony calls (with CALL_IDs and recording flags) |
  35. | `get_call_recording` | Recording URL/file + bound record for one call |
  36. | `list_pipelines` | Deal funnels (with stages) + smart processes |
  37. ### Writes (gated by `BITRIX_WRITES`)
  38. | Tool | Scope of write |
  39. | ---- | -------------- |
  40. | `add_lead_comment`, `rename_lead`, `set_lead_fields` | Leads |
  41. | `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** |
  42. | `create_contact`, `update_contact`, `create_company`, `update_company` | Parties, gated also by `BITRIX_WRITE_PARTIES`; create dup-checks first |
  43. | `transcribe_call` | Local transcription, opt-in via `BITRIX_TRANSCRIBE` |
  44. `set_lead_fields` / `set_deal_fields` take a `{label: value}` object and
  45. translate enum options, yes/no booleans, and dates automatically, e.g.
  46. `{"Follow-up Status": "Quote Sent", "Rush Job": "yes"}`.
  47. ## Webhook permissions (required)
  48. Create one **Inbound webhook** (Applications → Developer resources → Other →
  49. Inbound webhook) and enable exactly these scopes:
  50. | Scope | Powers |
  51. | ----- | ------ |
  52. | `crm` | all CRM reads + writes |
  53. | `user_brief` | assignee / author names (upgrade to `user` if names look thin) |
  54. | `task` | `list_tasks` |
  55. | `telephony` | `get_call_history`, `get_call_recording`, transcription |
  56. | `imopenlines` + `im` | website-chat read (commercial plan required) |
  57. Without `imopenlines`/`im`, chat tools degrade gracefully (timeline still works).
  58. Without `telephony`, the call tools return a clear "missing scope" message.
  59. > **CoPilot note.** Bitrix CoPilot's generated transcripts/summaries are **not**
  60. > readable over REST, and a call's `TRANSCRIPT_ID` has no getter. "CoPilot-style"
  61. > value here comes from **Claude** summarizing the data these tools expose
  62. > (chat transcripts, recordings, timeline), not from reading CoPilot itself.
  63. ## Security & opsec (read before distributing)
  64. - **The webhook URL is a password.** Anyone with it has the webhook user's full
  65. API access from anywhere (Bitrix cloud webhooks can't be IP-restricted).
  66. - Choose a webhook-owner model and stick to it:
  67. - **Per-user** (what the Windows install uses): each person's webhook lives in
  68. *their own* Bitrix account, so actions are attributable to them. Never copy
  69. one person's `.env` to another machine.
  70. - **Dedicated service user**: one non-admin "CRM Automation" account scoped to
  71. only what's needed: simplest least-privilege, but loses per-user audit.
  72. - Enable **2FA** on whichever account owns the webhook.
  73. - `chmod 600 .env` on every machine. Never commit `.env` (it's gitignored).
  74. - Have a **rotation plan**: if a machine is lost, delete + recreate the webhook
  75. in Bitrix and update `.env`.
  76. - **Token never logged.** httpx request logging is silenced and all error paths
  77. redact the webhook URL.
  78. - **Logs are PII-scrubbed by default.** `logs/calls.jsonl` records tool calls
  79. for migration analysis; `BITRIX_LOG_ARGS=redact` (default) masks emails/phones
  80. and blanks customer names/free text. Use `full` only on a trusted solo machine;
  81. `off` to log no arguments. The log self-rotates past `BITRIX_LOG_MAX_BYTES`.
  82. - **Untrusted content is fenced.** Customer/visitor free text (record fields,
  83. email/chat bodies) is wrapped as untrusted data so the model treats it as data,
  84. never instructions. Keep per-write confirmation on in Claude Desktop.
  85. ## Setup
  86. ```bash
  87. cd meridian-crm-mcp
  88. python3 -m venv .venv
  89. source .venv/bin/activate # Windows: .venv\Scripts\activate
  90. pip install -r requirements.txt
  91. cp .env.example .env
  92. # paste your webhook URL and adjust settings
  93. chmod 600 .env # macOS/Linux
  94. ```
  95. Smoke test: `python server.py` should log a startup line and wait silently.
  96. Ctrl+C to stop. Then wire it into Claude Desktop's `claude_desktop_config.json`
  97. under `mcpServers` with absolute paths to the venv python and `server.py`, and
  98. restart Claude Desktop.
  99. ## Checks
  100. ```bash
  101. pip install -r requirements.txt pytest ruff
  102. ruff check .
  103. python -m py_compile server.py
  104. pytest
  105. BITRIX_WEBHOOK_URL=https://example.invalid/rest/1/x BITRIX_DATA_DIR=$(mktemp -d) \
  106. python server.py # starts on stdio; Ctrl+C to stop
  107. cd mcpb && ./build.sh # mcpb validate + pack
  108. ```
  109. The tests need no webhook and make no network calls: `tests/conftest.py` sets
  110. the environment before importing `server.py` and swaps `bitrix_call` for a
  111. recording fake, and `bitrix_call` itself runs against `httpx.MockTransport`.
  112. `tests/tool_api.json` records the 30 tools' names, docstrings and parameter
  113. schemas, so any change to what the model sees fails the suite until the
  114. contract file is updated on purpose. CI runs lint, compile and tests on
  115. Python 3.10 and 3.12.
  116. ## Configuration
  117. All settings live in `.env` (see `.env.example` for the annotated list):
  118. `BITRIX_WEBHOOK_URL`, `BITRIX_LOCAL_TZ`, `BITRIX_WRITES`, `BITRIX_WRITE_FUNNELS`,
  119. `BITRIX_WRITE_PARTIES`, `BITRIX_LOG_ARGS`, `BITRIX_LOG_MAX_BYTES`,
  120. `BITRIX_TRANSCRIBE`.
  121. ## Architecture notes
  122. - **Universal API.** Reads use the `crm.item.*` family (entityTypeId: lead 1,
  123. deal 2, contact 3, company 4). The one exception is **party create/update**,
  124. which uses legacy `crm.contact.*` / `crm.company.*` because `crm.item.add`
  125. silently drops phone/email multifields.
  126. - **Batched counts.** `pipeline_summary` runs all per-stage counts in one
  127. `rest.batch` round-trip (chunked at 50). One shared `httpx.AsyncClient` pools
  128. connections across calls.
  129. - **Semantic tool names** (`search_crm`, not `bitrix_crm_search`) so the
  130. interface survives a future CRM migration; only the implementation swaps.
  131. - **Schema drift tracking.** Field schemas snapshot to `./schema/` (one dated
  132. file per entity per day) so form-builder changes are diffable over time.
  133. The snapshots are gitignored: they hold the live CRM's own field definitions.
  134. ## Troubleshooting
  135. - **`BITRIX_WEBHOOK_URL not set`**: `.env` missing/empty.
  136. - **`insufficient_scope`**: add the missing scope to the webhook (see table).
  137. - **Chat tools say insufficient_scope**: add `imopenlines` + `im` (commercial
  138. plan required).
  139. - **A deal write is "Refused… not a write-enabled funnel"**: the deal isn't in
  140. `BITRIX_WRITE_FUNNELS`; that's the funnel lock working as intended.
  141. - **Custom field shows as `UF_CRM_…`**: it's a custom field; `get_record` shows
  142. its label, and `get_fields` / `find_field` dump the schema.