gohighlevel-mcp
by alibhatti59
README.md
# gohighlevel-mcp
An MCP (Model Context Protocol) server that exposes GoHighLevel CRM
operations as tools an LLM agent can call: search contacts, get a
contact record, list appointments, log a note, and create a new lead.
## Why this server
I maintain production GoHighLevel sub-accounts, Vapi voice agents, and
website chatbots for three real-estate brands as part of my day job.
The recurring pattern in that work is: a caller or website visitor
talks to an agent, the agent needs to check if they're already a CRM
contact, see their history and upcoming appointments, log what
happened, and either update or create their record. This server
exposes exactly that loop as MCP tools, so any MCP-compatible agent
(Claude, an n8n AI agent node, a custom LangChain agent, etc.) can plug
straight into a GoHighLevel sub-account.
## Design choices
- **Python + FastMCP** (`mcp.server.fastmcp`) — matches the rest of my
stack (FastAPI, Python automation scripts) and gives automatic input
validation via Pydantic without hand-rolled JSON schemas.
- **Mock data backend for this build.** I don't have a spare GHL
sub-account API key to hand over for an evaluation task, so
`data_store.py` reads/writes a local `mock_data.json` instead of
calling `https://services.leadconnectorhq.com`. Every method in
`GHLDataStore` is named and shaped after the real GHL v2 endpoint it
stands in for (see the docstring at the top of `data_store.py`), so
swapping in an `httpx.AsyncClient` with a real API key is a
same-signature change, not a rewrite.
- **Tools, not raw endpoint wrappers.** `ghl_search_contacts` +
`ghl_get_contact` + `ghl_list_appointments` + `ghl_create_note` +
`ghl_create_contact` map onto real agent tasks (look someone up,
check their history, log the outcome), not a 1:1 dump of every GHL
API route.
- **Read-only tools are annotated `readOnlyHint: true`**; the two
write tools (`ghl_create_note`, `ghl_create_contact`) are not, so a
permission-gating layer upstream can treat them differently — which
matters a lot once an agent is allowed to touch real CRM data.
- **Errors are actionable, not stack traces.** A lookup on an unknown
`contact_id` returns `{"error": ..., "suggestion": "Use
ghl_search_contacts to find the correct contact_id."}` instead of
crashing the agent's turn.
## Project structure
```
gohighlevel-mcp/
├── server.py # MCP server + tool definitions (FastMCP)
├── data_store.py # Data access layer (mock JSON now, real GHL API later)
├── mock_data.json # Sample CRM data: 3 contacts, 2 appointments
├── test_server.py # End-to-end test: spins up server.py as a real
│ # stdio subprocess and calls every tool through
│ # a proper MCP ClientSession
└── requirements.txt
```
## Run it
```bash
pip install -r requirements.txt
python server.py # stdio transport, for local MCP clients
python server.py --http # streamable HTTP on :8000, for remote clients
```
## Test it
```bash
python test_server.py
```
This launches the server as a real subprocess over stdio, connects a
real `ClientSession`, calls `list_tools`, then exercises all 5 tools
(search → get → list appointments → add note → handle a bad id
gracefully → create a new lead) and asserts on the results. Not a
mocked client — this is the same protocol path a real agent uses.
## What I'd add next
- Swap `GHLDataStore` for an `httpx.AsyncClient`-backed version that
calls the real GHL API with a Location API key from an env var.
- Pagination cursor support to match GHL's actual cursor-based
pagination (this mock uses simple offset/limit for clarity).
- A `ghl_update_pipeline_stage` tool, since stage changes are the other
half of the lead-routing loop I handle day to day.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues