GHL MCP Server
A production-grade MCP server that wraps the GoHighLevel API v2, enabling Claude and other MCP clients to interact with GHL CRM as structured tools for managing contacts, conversations, pipelines, appointments, payments, and more.
README
GHL MCP Server
A production-grade Model Context Protocol (MCP) server that wraps the GoHighLevel (GHL) API v2, enabling Claude and other MCP clients to interact with GHL CRM as structured tools — creating contacts, sending messages, managing pipelines, booking appointments, and more.
Overview
This server exposes 113 tools as MCP tools — 111 GHL API tools across 17 modules, plus 2 AI agent tools that chain multiple GHL operations autonomously.
| Module | Tools | Description |
|---|---|---|
| Contacts | 14 | CRUD, search, tags, notes, tasks, upsert |
| Conversations | 7 | List threads, send SMS/email, get messages, mark read, reports |
| Opportunities | 9 | Pipeline management, stage moves, CRUD |
| Calendars | 15 | Calendars, calendar groups, slots, book/update/cancel appointments |
| Payments | 11 | Products, invoices, transactions, subscriptions |
| Workflows | 3 | List, enroll/remove contacts from automations |
| Forms & Surveys | 4 | List forms/surveys, get submissions |
| Users | 5 | CRUD team members |
| Locations | 16 | Sub-account management, custom values, custom fields, tags |
| Media | 3 | Upload, list, delete media library files |
| Links | 5 | Custom tracking link management |
| Blogs | 5 | Blog post management |
| Funnels | 5 | Funnel and funnel-page management |
| Snapshots | 2 | View cloneable account snapshots |
| Documents | 4 | List, send docs/templates |
| SaaS | 3 | Plans, subscriptions, enable SaaS mode |
| AI Agents | 2 | Multi-step sales automation with LLM reasoning and HITL approval |
| Webhooks | inbound | Signature-validated event router |
Prerequisites
- Python 3.11+
- A GoHighLevel account with API access
- (For OAuth) A GHL Marketplace app with OAuth credentials
Installation
# 1. Clone the repository
git clone https://github.com/RohitashAery/ghl-mcp-server.git
cd ghl-mcp-server
# 2. Copy and configure environment
cp .env.example .env
# Edit .env with your credentials (see configuration section below)
# 3. Install dependencies
pip install -e .
# 4. Create data directory for OAuth token storage
mkdir -p data
# 5. Start the server
python main.py
The server starts at http://localhost:8000. Visit http://localhost:8000/docs for the Swagger UI.
Configuration
All configuration is via environment variables (or .env file):
| Variable | Default | Description |
|---|---|---|
GHL_AUTH_MODE |
private |
Auth mode: private or oauth |
GHL_PRIVATE_TOKEN |
— | Private integration token (private mode) |
GHL_LOCATION_ID |
— | Default sub-account location ID |
GHL_CLIENT_ID |
— | OAuth app client ID |
GHL_CLIENT_SECRET |
— | OAuth app client secret |
GHL_REDIRECT_URI |
http://localhost:8000/oauth/callback |
OAuth callback URL |
MCP_TRANSPORT |
http |
Transport: http or stdio |
HOST |
0.0.0.0 |
HTTP server bind host |
PORT |
8000 |
HTTP server port |
GHL_WEBHOOK_SECRET |
— | HMAC secret for webhook validation |
LOG_LEVEL |
INFO |
DEBUG, INFO, WARNING, ERROR |
LOG_FORMAT |
json |
json (production) or console (development) |
DB_PATH |
./data/tokens.db |
SQLite path for OAuth tokens |
ALLOWED_MODULES |
all |
Comma-separated module names to expose, or all. Used for plan gating in hosted SaaS. |
ALLOWED_API_KEYS |
(empty) | Comma-separated API keys required on /mcp/sse. Empty disables the check. Used for Claude seat enforcement. |
LLM_PROVIDER |
anthropic |
LLM backend for AI agents: anthropic or openai |
ANTHROPIC_API_KEY |
— | Anthropic API key (required if LLM_PROVIDER=anthropic) |
OPENAI_API_KEY |
— | OpenAI API key (required if LLM_PROVIDER=openai) |
LLM_MODEL |
claude-sonnet-4-6 |
Model name passed to the LLM provider |
AGENT_CHECKPOINTER_DB |
./data/agent_checkpoints.db |
SQLite path for LangGraph HITL checkpoint state |
CHROMA_PERSIST_DIR |
./data/chroma |
ChromaDB persistence directory for semantic vector search (Phase 2) |
SaaS Hosting (Multi-client)
This server supports running as a managed hosted service with multiple clients on shared infrastructure. Each client gets an isolated container with their own credentials and plan-gated tool set.
Plan gating
Set ALLOWED_MODULES to a comma-separated list of module names:
# Tier 1 — 48 tools
ALLOWED_MODULES=contacts,conversations,opportunities,pipelines,calendars,forms
# Tier 2 — 88 tools
ALLOWED_MODULES=contacts,conversations,opportunities,pipelines,calendars,payments,invoices,transactions,subscriptions,workflows,forms,surveys,users,media,links,blogs,funnels,documents
# Tier 3 — 113 tools (default: all GHL tools + AI agents)
ALLOWED_MODULES=all
Claude only sees the tools in the allowed modules — blocked modules are invisible.
Claude seat enforcement
Set ALLOWED_API_KEYS to a comma-separated list of bearer tokens:
ALLOWED_API_KEYS=key-abc123,key-xyz789
Clients include their key in Claude Desktop config:
{
"mcpServers": {
"ghl": {
"type": "sse",
"url": "https://client.yourdomain.com/mcp/sse",
"headers": { "Authorization": "Bearer key-abc123" }
}
}
}
See docs/saas-deployment.md for the full per-tier setup guide and docs/aws-deployment.md for the AWS ECS infrastructure guide.
Private Token Setup
- Log into GoHighLevel
- Go to Settings → Integrations → Private Integrations
- Click Create New Integration
- Give it a name and select all required scopes
- Copy the generated token
- Set
GHL_PRIVATE_TOKEN=<token>in your.env - Set
GHL_AUTH_MODE=private
OAuth Setup
Step 1: Create a GHL Marketplace App
- Go to GHL Marketplace
- Click + Create App
- Set the Redirect URI to your server's callback URL (e.g.
https://your-server.com/oauth/callback) - Under Scopes, select all scopes listed in
.env.example - Save — copy your Client ID and Client Secret
Step 2: Configure your server
GHL_AUTH_MODE=oauth
GHL_CLIENT_ID=your_client_id
GHL_CLIENT_SECRET=your_client_secret
GHL_REDIRECT_URI=http://localhost:8000/oauth/callback
Step 3: Authorize
- Start the server:
python main.py - Open
http://localhost:8000/oauth/authorizein your browser - Select the GHL location to authorize
- After approval, you're redirected to
/oauth/callback - Token is stored in SQLite — the server auto-refreshes before expiry
Claude Desktop Setup (stdio mode)
Set MCP_TRANSPORT=stdio in your .env, then add to your Claude Desktop config:
macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
Windows: %APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"ghl": {
"command": "python",
"args": ["/absolute/path/to/ghl-mcp-server/main.py"],
"env": {
"MCP_TRANSPORT": "stdio",
"GHL_AUTH_MODE": "private",
"GHL_PRIVATE_TOKEN": "your_token_here",
"GHL_LOCATION_ID": "your_location_id",
"LOG_FORMAT": "console"
}
}
}
}
Restart Claude Desktop. The GHL tools will appear in the tools panel.
Claude.ai / N8N Setup (HTTP/SSE mode)
Set MCP_TRANSPORT=http and start the server. The SSE endpoint is:
http://your-server:8000/mcp/sse
Claude.ai connector: Add the SSE URL in Claude's connector settings.
N8N: Use the MCP Client node with the SSE endpoint URL.
Swagger UI: http://your-server:8000/docs
All Tools Reference
Contacts
| Tool | Description | Required Params |
|---|---|---|
ghl_contacts_create |
Create a new contact | — (at least one of: email, phone) |
ghl_contacts_get |
Get contact by ID | contactId |
ghl_contacts_search |
Search contacts | — |
ghl_contacts_update |
Update contact fields | contactId, fields |
ghl_contacts_delete |
Delete a contact | contactId |
ghl_contacts_add_tag |
Add tags to contact | contactId, tags |
ghl_contacts_remove_tag |
Remove tags from contact | contactId, tags |
ghl_contacts_add_note |
Add a note | contactId, body |
ghl_contacts_list_notes |
List contact notes | contactId |
ghl_contacts_add_task |
Create a task | contactId, title, dueDate |
ghl_contacts_list_tasks |
List contact tasks | contactId |
ghl_contacts_upsert |
Create or update by email/phone | email or phone |
Conversations
| Tool | Description | Required Params |
|---|---|---|
ghl_conversations_list |
List conversation threads | — |
ghl_conversations_get |
Get a conversation | conversationId |
ghl_conversations_send_sms |
Send SMS to contact | contactId, message |
ghl_conversations_send_email |
Send email to contact | contactId, subject, body |
ghl_conversations_get_messages |
Get messages in thread | conversationId |
ghl_conversations_mark_read |
Mark conversation read | conversationId |
Opportunities
| Tool | Description | Required Params |
|---|---|---|
ghl_opportunities_list |
List opportunities | — |
ghl_opportunities_get |
Get opportunity | opportunityId |
ghl_opportunities_create |
Create opportunity | pipelineId, stageId, contactId, name |
ghl_opportunities_update |
Update opportunity | opportunityId, fields |
ghl_opportunities_update_stage |
Move to stage | opportunityId, stageId |
ghl_opportunities_delete |
Delete opportunity | opportunityId |
ghl_pipelines_list |
List all pipelines | — |
Calendars & Appointments
| Tool | Description | Required Params |
|---|---|---|
ghl_calendars_list |
List calendars | — |
ghl_calendars_get_slots |
Get available slots | calendarId, startDate, endDate |
ghl_appointments_list |
List appointments | — |
ghl_appointments_create |
Book appointment | calendarId, contactId, startTime, endTime |
ghl_appointments_update |
Update appointment | appointmentId, fields |
ghl_appointments_cancel |
Cancel appointment | appointmentId |
Payments
| Tool | Description | Required Params |
|---|---|---|
ghl_payments_list_products |
List products | — |
ghl_invoices_create |
Create invoice | contactId, items |
ghl_invoices_list |
List invoices | — |
ghl_transactions_list |
List transactions | — |
ghl_subscriptions_list |
List subscriptions | — |
ghl_subscriptions_get |
Get subscription | subscriptionId |
Workflows
| Tool | Description | Required Params |
|---|---|---|
ghl_workflows_list |
List workflows | — |
ghl_workflows_add_contact |
Enroll contact in workflow | workflowId, contactId |
ghl_workflows_remove_contact |
Remove from workflow | workflowId, contactId |
Forms
| Tool | Description | Required Params |
|---|---|---|
ghl_forms_list |
List forms | — |
ghl_forms_get_submissions |
Get form submissions | formId |
Users
| Tool | Description | Required Params |
|---|---|---|
ghl_users_list |
List team members | — |
ghl_users_get |
Get user by ID | userId |
ghl_users_create |
Create user | firstName, lastName, email |
ghl_users_update |
Update user | userId, fields |
ghl_users_delete |
Delete user | userId |
Locations
| Tool | Description | Required Params |
|---|---|---|
ghl_locations_list |
List sub-accounts | companyId |
ghl_locations_get |
Get location | locationId |
ghl_locations_create |
Create location | companyId, name |
ghl_locations_update |
Update location | locationId, fields |
Documents
| Tool | Description | Required Params |
|---|---|---|
ghl_documents_list |
List documents | — |
ghl_documents_send |
Send document | documentId, contactId |
ghl_templates_list |
List templates | — |
ghl_templates_send |
Send template | templateId, contactId |
SaaS
| Tool | Description | Required Params |
|---|---|---|
ghl_saas_list_plans |
List SaaS plans | companyId |
ghl_saas_get_subscription |
Get subscription status | — |
ghl_saas_enable |
Enable SaaS for location | planId |
AI Agents
Multi-step autonomous agents powered by LangGraph. Each agent fetches data from multiple GHL domains, uses an LLM to reason about the best action, and executes it. High-stakes write actions (send SMS/email, move pipeline stage, enroll workflow) pause for human approval before executing.
| Tool | Description | Required Params |
|---|---|---|
ghl_agent_sales_automation |
Run a full sales automation sequence for a contact: fetch contact + opportunities + conversations → LLM analysis → execute action (or pause for approval) | contact_id |
ghl_agent_approve_action |
Approve or reject a high-stakes action proposed by a suspended agent run | thread_id, approved |
How the HITL (Human-in-the-Loop) flow works:
- Call
ghl_agent_sales_automation(contact_id="abc123")— agent fetches data and reasons over it - If the LLM proposes a high-stakes action (send SMS, move pipeline, etc.), it returns:
{ "status": "awaiting_approval", "thread_id": "uuid", "proposed_action": {...}, "reasoning": "..." } - Review the proposed action, then call
ghl_agent_approve_action(thread_id="uuid", approved=true)to execute it orapproved=falseto cancel - For low-stakes actions (add note, add tag), the agent executes immediately and returns
"status": "completed"
To resume a previously suspended run without re-running data fetching, pass thread_id to ghl_agent_sales_automation.
LLM setup: Set LLM_PROVIDER, ANTHROPIC_API_KEY (or OPENAI_API_KEY), and LLM_MODEL in your .env. The agent layer is model-agnostic — swap providers without changing any agent code.
Webhook Setup
- In GHL: go to Settings → Webhooks → Add Webhook
- Set the URL to
https://your-server.com/webhooks/ghl - Copy the Signing Secret from GHL and set
GHL_WEBHOOK_SECRET=<secret>in.env - Select the event types you want to receive
Supported Events
| Event Type | Triggered When |
|---|---|
ContactCreate |
New contact created |
ContactUpdate |
Contact fields changed |
ContactDelete |
Contact deleted |
OpportunityCreate |
New opportunity created |
OpportunityUpdate |
Opportunity updated |
OpportunityStatusChange |
Opportunity won/lost/etc. |
InboundMessage |
Contact sent a message |
OutboundMessage |
Message sent to contact |
AppointmentCreate |
Appointment booked |
AppointmentUpdate |
Appointment changed |
NoteCreate |
Note added to contact |
TaskCreate |
Task added to contact |
FormSubmission |
Form submitted |
PaymentSuccess |
Payment completed |
Custom Event Handlers
In mcp/webhooks.py, add your own logic:
from mcp.webhooks import webhook_handler
@webhook_handler("ContactCreate")
async def my_handler(event: dict) -> None:
contact_id = event.get("id")
# your custom logic here
Docker
# Build and start
docker-compose up -d
# View logs
docker-compose logs -f ghl-mcp-server
# Stop
docker-compose down
The data/ directory is mounted as a volume to persist OAuth tokens across restarts.
Running Tests
# Install dev dependencies
pip install -e ".[dev]"
# Run all tests with coverage
pytest
# Run specific module
pytest tests/test_contacts.py -v
# Run with HTML coverage report
pytest --cov-report=html
# Open htmlcov/index.html in browser
How to Add a New Tool
-
Add the API method in
api/<module>.py:async def my_new_action(self, location_id: str, param: str) -> dict[str, Any]: return await self._client.post("/endpoint", location_id=location_id, json={"param": param}) -
Add the Pydantic model in
models/<module>.py(if new response shape):class MyNewModel(BaseModel): id: str field: str -
Add the Tool definition in
mcp/tools/<module>.py— append to the_<module>_tools()list:Tool( name="ghl_module_my_new_action", description="Clear description of what this does and when to use it.", inputSchema={ "type": "object", "properties": { "locationId": {"type": "string"}, "param": {"type": "string", "description": "What this param does"}, }, "required": ["param"], }, ), -
Add the dispatch case in
_dispatch()in the same file:elif name == "ghl_module_my_new_action": result = await api.my_new_action(location_id, arguments["param"]) -
Write a test in
tests/test_<module>.py:@pytest.mark.asyncio async def test_my_new_action(module_api, mock_ghl): mock_ghl.post("/endpoint").mock(return_value=httpx.Response(200, json={"ok": True})) result = await module_api.my_new_action("loc_id", "value") assert result["ok"] is True -
Register the tool — it's already wired up via
mcp/server.pydispatch based on prefix.
Rate Limits
GHL enforces:
- Burst limit: 100 requests per 10 seconds per location
- Daily limit: ~200,000 requests per day
This server handles both automatically:
- Token bucket per location: waits for refill if empty (never drops requests)
- Daily warning: logs a warning at 80% of daily limit
- Retry on 429: exponential backoff with jitter (up to 3 retries)
When the rate limit is hit, the tool call will wait (up to ~7 seconds across retries) rather than fail immediately.
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
AuthenticationError: token invalid |
GHL_PRIVATE_TOKEN is wrong or expired |
Regenerate token in GHL Settings |
ConfigurationError: locationId required |
No locationId passed or GHL_LOCATION_ID not set |
Set GHL_LOCATION_ID in .env or pass locationId in every tool call |
NotFoundError |
Resource ID doesn't exist in this location | Verify the ID belongs to the correct location |
ValidationError (422) |
Required GHL fields missing or wrong format | Check GHL API docs for required fields |
GHLServerError (500/502/503) |
GHL API is down | Server retries up to 3 times with backoff |
OAuth: No token found for location |
OAuth flow not completed | Visit /oauth/authorize and complete authorization |
OAuth: Token refresh failed |
Refresh token expired (>60 days unused) | Re-authorize via /oauth/authorize |
Webhook: 401 Invalid signature |
GHL_WEBHOOK_SECRET mismatch |
Copy exact secret from GHL webhook settings |
ImportError: mcp not found |
MCP SDK not installed | Run pip install -e . |
Agent: LLM_PROVIDER not set |
AI agent tools called without LLM config | Set LLM_PROVIDER + ANTHROPIC_API_KEY or OPENAI_API_KEY in .env |
Agent: No pending approval for thread |
ghl_agent_approve_action called with stale ID |
Restart with a new ghl_agent_sales_automation call |
Agent: contact_id is required |
Called ghl_agent_sales_automation without ID |
Pass a valid GHL contact_id |
Architecture
main.py ← Entrypoint; starts stdio or HTTP transport
config.py ← pydantic-settings; all env config
auth/
private_token.py ← Bearer token injection
oauth.py ← Auth code grant, token refresh, SQLite storage
api/
client.py ← httpx AsyncClient; retry, rate-limit, error mapping
contacts.py ← Raw GHL API calls (no business logic)
...
ghl_mcp/
server.py ← MCP Server; registers all 113 tools
tools/
contacts.py ← Tool definitions + dispatch for contacts
... (17 domain tool files, all untouched by agent layer)
agents/ ← LangGraph agent layer (NEW)
base.py ← GHLAgentContext, get_llm(), truncate_text()
sales_automation.py ← LangGraph StateGraph: 7 nodes + HITL interrupt
registry.py ← MCP tool definitions + agent dispatch router
checkpointer.py ← MemorySaver singleton for HITL state persistence
tools.py ← LangChain StructuredTool wrappers (for future ReAct agents)
webhooks.py ← FastAPI router; signature validation + event routing
models/ ← Pydantic v2 models for GHL response shapes
tests/ ← pytest with respx mock for every module
Request flow — GHL tool (HTTP mode):
Claude → SSE /mcp/sse → MCP Server → tool dispatch → API module → GHLClient → GHL API v2
Request flow — AI agent tool:
Claude → SSE /mcp/sse → MCP Server → _agent_dispatch → LangGraph StateGraph
→ [fetch_contact] → [fetch_opportunities] → [fetch_conversations]
→ [analyze_and_plan (LLM)] → [request_approval (interrupt)] or [execute_action]
→ API module → GHLClient → GHL API v2
Agent HITL (Human-in-the-Loop) checkpoint flow:
ghl_agent_sales_automation → interrupt() → MemorySaver saves state
→ returns "awaiting_approval"
ghl_agent_approve_action → Command(resume=approved) → graph resumes
→ execute_action → completed
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.
E2B
Using MCP to run code via e2b.