chatwoot-mcp
# chatwoot-mcp
An MCP (Model Context Protocol) server that exposes [Chatwoot](https://www.chatwoot.com/) analytics data to AI assistants. All tools are read-only, except `chatwoot_get_conversation_summary` which triggers a Captain LLM call on the Chatwoot server.
## Features
- **39 MCP tools** covering conversations, agents, inboxes, teams, labels, CSAT, Captain AI, real-time metrics, and contacts
- **Local DuckDB storage**: sync a recent window of conversations/contacts/messages for offline analytics
- **Jev lead scoring**: score contacts by propensity to enroll using [Jev](https://openrouter.ai/typesafe/jev-1.13) (TypeSafe System One) via OpenRouter, with built-in calibration
- **Configurable domain profile**: generic defaults; map an account's own attribute keys, labels, stages and keywords via a gitignored JSON profile
- **Two transport modes**: stdio for Claude Desktop, HTTP for claude.ai remote connector
- **Multi-tenant HTTP mode**: each user provides their own Chatwoot credentials per request; each tenant gets an isolated DuckDB file
- **Dual output format**: `markdown` (human-readable) or `json` for all tools
- **Period shortcuts**: built-in ranges like `today`, `7d`, `30d`, `90d` for all time-series queries
## Requirements
- Python 3.11+
- [uv](https://docs.astral.sh/uv/) package manager
- A Chatwoot account with API access
## Installation
```bash
git clone https://github.com/minholi/chatwoot-mcp.git
cd chatwoot-mcp
uv sync
```
## Configuration
Copy `.env.example` to `.env` and fill in your credentials:
```bash
cp .env.example .env
```
```env
CHATWOOT_URL=https://your-instance.chatwoot.com
CHATWOOT_ACCOUNT_ID=1
CHATWOOT_API_TOKEN=your_api_token_here
```
Your API token can be found in Chatwoot under **Profile Settings → Access Token**.
### Local storage & lead scoring (optional)
The DuckDB sync and Jev lead scoring features are optional and configured separately:
```env
# Per-tenant DuckDB files live under data/tenants/<hash>.duckdb
CHATWOOT_DB_DIR=./data
# Optional fixed path (overrides CHATWOOT_DB_DIR; useful for stdio single-tenant)
# CHATWOOT_DB_PATH=./data/chatwoot.duckdb
JEV_SYNC_MAX_PAGES=8000
JEV_SYNC_STOP_MARGIN=10
# Throttling — protects the Chatwoot instance during syncs
CHATWOOT_MAX_RPS=3
CHATWOOT_MAX_RETRIES=4
# Jev (TypeSafe System One) via OpenRouter — https://openrouter.ai
OPENROUTER_API_KEY=
OPENROUTER_BASE_URL=https://openrouter.ai
JEV_MODEL=typesafe/jev-1.13
```
Typical flow:
1. `chatwoot_sync_local_data` — ingest a recent window into DuckDB.
2. `chatwoot_score_leads` — compute features and call Jev per lead (billable, cheap).
3. `chatwoot_list_hot_leads` / `chatwoot_lead_scoring_report` — consume the ranking.
4. `chatwoot_evaluate_lead_scoring` — backtest against historical outcomes.
> **Long backfills:** MCP clients time out on multi-minute tool calls, so large
> first-time syncs are better run as a detached CLI process calling
> `src.ingest.sync_all(...)`; the MCP tool is fine for small incremental refreshes.
> Requests are paced globally by `CHATWOOT_MAX_RPS` with retry/backoff, and the
> DuckDB store is per-tenant and safe to re-run (upserts).
### Domain profile (account-specific vocabulary)
Lead scoring relies on an account's own enrollment vocabulary. The tracked
defaults are a generic example; supply a JSON profile to map the logical names
to a real account's Chatwoot custom-attribute keys, labels, stages and keywords:
```json
{
"institution_name": "Example University",
"attributes": {
"stage": "enrollment_stage",
"situation": "enrollment_status",
"course": "course_code",
"course_name": "course_name",
"modality": "modality"
},
"label_flags": {
"has_enrolled": { "exact": ["enrolled"] },
"has_no_response": { "prefix": ["no-response"] }
},
"keywords": { "kw_enroll": "enroll|register|sign up" }
}
```
Point `CHATWOOT_DOMAIN_CONFIG` at the file (or set `JEV_INSTITUTION_NAME` to just
override the deployment name). Only the keys you provide are overridden; the rest
fall back to the defaults. Logical names are stable, so keep the profile out of
version control (the `domain/` directory is gitignored).
## Usage
### Stdio mode (Claude Desktop)
Run the server directly — credentials come from the `.env` file:
```bash
uv run python main.py
```
To integrate with Claude Desktop, add this to your `claude_desktop_config.json`:
```json
{
"mcpServers": {
"chatwoot": {
"command": "uv",
"args": ["run", "python", "main.py"],
"cwd": "/path/to/chatwoot-mcp",
"env": {
"CHATWOOT_URL": "https://your-instance.chatwoot.com",
"CHATWOOT_ACCOUNT_ID": "1",
"CHATWOOT_API_TOKEN": "your_api_token_here"
}
}
}
}
```
### HTTP mode (claude.ai remote connector)
Start the server in HTTP mode:
```bash
uv run python main.py --transport http --port 8000
```
Each request must include a composite key in the `Authorization` or `X-API-Key` header:
```
Authorization: Bearer https://your-instance.chatwoot.com|account_id|api_token
```
This allows multiple users to connect with their own credentials without server-side configuration.
Available endpoints:
| Endpoint | Description |
|----------|-------------|
| `GET /` | Service info |
| `GET /health` | Health check |
| `POST /mcp` | MCP protocol (stateless) |
### Docker
```bash
docker compose up -d
```
The compose file reads from `.env` and runs in HTTP mode on `http://127.0.0.1:8000` by default. Set `MCP_HOST_PORT` in `.env` to change the exposed port.
## Available tools
### Overview
| Tool | Description |
|------|-------------|
| `chatwoot_get_summary` | Account summary: conversation counts, response times, resolution rate, CSAT |
| `chatwoot_get_bot_summary` | Bot performance: handoffs, autonomous resolution rate, average response time |
| `chatwoot_get_conversation_traffic` | Heat map of conversation volume by hour and day of week |
### Agents
| Tool | Description |
|------|-------------|
| `chatwoot_list_agents` | List all agents with ID, email, and availability status |
| `chatwoot_get_agent_performance` | Per-agent metrics: open/resolved conversations, response time, CSAT |
### Conversations
| Tool | Description |
|------|-------------|
| `chatwoot_list_conversations` | List conversations with filters (status, assignee, inbox, team, label) |
| `chatwoot_search_conversations` | Full-text search across conversations and messages |
| `chatwoot_get_conversation` | Full details of a specific conversation |
| `chatwoot_get_conversation_messages` | Complete message history of a conversation |
### Labels
| Tool | Description |
|------|-------------|
| `chatwoot_list_labels` | List all labels with ID, color, and description |
| `chatwoot_get_label_performance` | Performance metrics for a specific label |
### Inboxes
| Tool | Description |
|------|-------------|
| `chatwoot_list_inboxes` | List all inboxes (WhatsApp, email, widget, etc.) |
| `chatwoot_get_inbox_performance` | Performance metrics for a specific inbox |
### Teams
| Tool | Description |
|------|-------------|
| `chatwoot_list_teams` | List all teams |
| `chatwoot_get_team_performance` | Performance metrics for a specific team |
### Captain AI
| Tool | Description |
|------|-------------|
| `chatwoot_list_captain_assistants` | List AI assistants configured in the account |
| `chatwoot_get_captain_metrics` | AI assistant performance metrics |
| `chatwoot_get_captain_faq_stats` | FAQ usage and effectiveness statistics |
| `chatwoot_get_captain_overview` | Period-to-period comparison overview |
| `chatwoot_get_captain_resolution_flow` | Funnel of resolution steps |
| `chatwoot_get_captain_resolution_trend` | Resolution rate trend over time |
| `chatwoot_get_conversation_summary` | AI-generated summary of a conversation (**not read-only** — triggers a Captain LLM call, billable) |
### CSAT
| Tool | Description |
|------|-------------|
| `chatwoot_get_csat_responses` | Survey responses with filters (period, rating, inbox, team) |
| `chatwoot_get_csat_metrics` | Aggregated CSAT summary with breakdown by rating |
### Real-time
| Tool | Description |
|------|-------------|
| `chatwoot_get_live_metrics` | Real-time counts: open, unattended, unassigned, pending |
| `chatwoot_get_live_grouped_metrics` | Real-time conversations grouped by team or agent |
### Contacts
| Tool | Description |
|------|-------------|
| `chatwoot_search_contacts` | Search contacts by name, email, phone, or identifier |
| `chatwoot_get_contact` | Full contact details |
| `chatwoot_get_contact_conversations` | Conversation history of a contact |
### Advanced reports
| Tool | Description |
|------|-------------|
| `chatwoot_get_inbox_label_matrix` | Cross-distribution of conversations between inboxes and labels |
| `chatwoot_get_first_response_distribution` | Histogram of first-response-time distribution |
### Local storage (DuckDB)
| Tool | Description |
|------|-------------|
| `chatwoot_sync_local_data` | Incrementally sync conversations/contacts/messages into the local DuckDB store |
| `chatwoot_get_sync_status` | Row counts and sync cursors for the current tenant |
| `chatwoot_query_local_data` | Read-only SQL (SELECT/WITH) against the local store |
### Lead scoring (Jev)
| Tool | Description |
|------|-------------|
| `chatwoot_score_leads` | Score leads by propensity to enroll with Jev (**not read-only** — billable OpenRouter calls) |
| `chatwoot_list_hot_leads` | Ranked scored leads with filters (band, course, modality, confidence) |
| `chatwoot_get_lead_profile` | Local features + latest Jev decision for a contact |
| `chatwoot_lead_scoring_report` | Ranked lead-scoring report as a tool |
| `chatwoot_evaluate_lead_scoring` | Backtest persisted scores against historical outcomes (precision/recall/precision@K) |
## Development
```bash
# Lint and format
uv run ruff check src/ main.py
uv run ruff format src/ main.py
# Run tests
uv run pytest
```
## License
MIT — see [LICENSE](LICENSE).
TDQS
Scored across 30 tools
Each tool has a clearly distinct purpose: list vs. get vs. search, and per-entity performance metrics are separated by resource (agent, inbox, team, label, captain). Even the Captain analytics tools (FAQ stats, overview, resolution flow, resolution trend) are differentiated by their focus, reducing selection ambiguity.
All tools follow a strict chatwoot_<verb>_<noun> pattern with consistent verbs (get, list, search). The naming is uniform across all 30 tools, making the API predictable and easy to navigate.
At 30 tools, the server is on the heavier side, but the breadth of Chatwoot's feature set (agents, inboxes, teams, labels, contacts, conversations, CSAT, Captain AI, live metrics, analytics) justifies the count. Each tool covers a distinct aspect, so the number feels appropriate for a comprehensive analytics-focused MCP.
The server is strong for read-only analytics and reporting, covering the main entities and metrics. Minor gaps exist: no single-entity detail tools beyond list (e.g., get_inbox, get_team), and no write operations, but that aligns with its apparent reporting purpose. The core lifecycle of querying and analyzing data is well covered.