@aiwerk/mcp-server-ghl
# @aiwerk/mcp-server-ghl
MCP server for the [GoHighLevel](https://www.gohighlevel.com) (GHL) API, the CRM and
marketing automation platform used by agencies to run their clients' sales pipelines,
calendars, conversations and campaigns.
569 tools across 41 domains, generated from GHL's official OpenAPI 3.0.0 specification.
```
Contacts Opportunities Conversations Calendars Invoices
Payments Workflows Campaigns Forms Surveys
Funnels Blogs Courses Products Store
Social Media Ad Manager SaaS API Snapshots Custom Fields
```
## Why generated
Every endpoint, HTTP verb, parameter and field name comes from the official
specification rather than from prose documentation, so the tool surface can't drift
from what GHL actually accepts. What the specification can't tell you, which
endpoints need an agency-level token instead of a location one, which API version an
endpoint expects, which fields the docs forgot to mark required, is layered on top
by hand. See [GHL specifics worth knowing](#a-few-ghl-specifics-worth-knowing).
## Install
```bash
npm install -g @aiwerk/mcp-server-ghl
```
Requires Node.js 18 or newer.
## Authentication
Create a **Private Integration Token** (PIT) in the target location under
*Settings > Private Integrations*. A PIT is scoped to one location, it is not an
agency-wide credential, and most tools need to know which location they're acting on.
```bash
export GHL_PIT_TOKEN="your-private-integration-token"
export GHL_LOCATION_ID="your-location-id"
```
## Usage
### Claude Code
```bash
claude mcp add ghl \
--env GHL_PIT_TOKEN=your-token \
--env GHL_LOCATION_ID=your-location-id \
-- npx -y @aiwerk/mcp-server-ghl
```
### Claude Desktop
```json
{
"mcpServers": {
"ghl": {
"command": "npx",
"args": ["-y", "@aiwerk/mcp-server-ghl"],
"env": {
"GHL_PIT_TOKEN": "your-token",
"GHL_LOCATION_ID": "your-location-id"
}
}
}
}
```
### AIWerk hosted service
Install it from the catalogue at [aiwerkmcp.com](https://aiwerkmcp.com) and add your
token in the interface. No local setup required.
## Safety features
### Dry run
```bash
export GHL_DRY_RUN=1
```
Every write (`POST`/`PUT`/`PATCH`/`DELETE`) is stopped before it reaches GHL and
returns a description of the request that would have been sent. Reads still work
normally.
### Agency-only endpoints get a clear error, not a bare 401
39 endpoints (snapshots, the SaaS API, agency OAuth token exchange, creating custom
objects) require an agency-level token. A location PIT gets a plain `401` from GHL for
these with no explanation in the body, the server knows which endpoints these are and
returns a message saying so, instead of making it look like a bad or expired token.
### locationId is filled in automatically
A PIT is already scoped to one location, so 430 of the 569 tools accept `locationId`
(or `altId`/`altType`) as an *optional* parameter, if the calling agent doesn't supply
one, the server falls back to `GHL_LOCATION_ID`. This also means a tool call can't
accidentally target the wrong location by a copy-pasted id from a different account,
since the default always matches the token's own scope.
## Configuration
| Variable | Default | Purpose |
|---|---|---|
| `GHL_PIT_TOKEN` | required | Private Integration Token |
| `GHL_LOCATION_ID` | required | Location the PIT is scoped to; default for `locationId`/`altId` params |
| `GHL_API_BASE_URL` | `https://services.leadconnectorhq.com` | Override the host |
| `GHL_API_TIMEOUT_MS` | `30000` | Per request timeout |
| `GHL_DRY_RUN` | off | `1` blocks all writes |
| `GHL_MAX_RATE_LIMIT_WAIT_MS` | `10000` | Longest wait before failing on a rate limit |
| `GHL_ENABLED_TAGS` | all | Comma separated domain filter, for example `contacts,invoices` |
### Narrowing the tool set
All 569 tools are registered by default. A client that prefers a smaller surface can
restrict the server to specific domains (domain names are hyphenated, e.g.
`social-media-posting`, `ad-manager`):
```bash
export GHL_ENABLED_TAGS="contacts,opportunities,conversations,calendars"
```
Unknown domain names are reported on startup rather than silently ignored.
## A few GHL specifics worth knowing
- **The API version differs per endpoint, not globally.** GHL sends a `Version`
request header (`2021-07-28` or `2021-04-15`) that the server sets per call based on
what each endpoint actually expects, a wrong version returns a *different response
shape* silently, not an error, so there's no single default to fall back on. 29
endpoints send no version header at all; the server matches that too.
- **A location PIT cannot call agency-only endpoints, ever, no scope fixes it.**
`snapshots/*`, `saas-api/*`, `oauth/locationToken`, `oauth/installedLocations`, and
creating custom objects (`POST /objects`) need an agency-level credential.
- **11 endpoints in the official spec omit a path parameter's declaration** (e.g. a
`noteId` on some calendar/conversation routes, a `postId` on blogs, a `type` on
contacts). The generator fills these in as required string fields since the
parameter is clearly used in the path template, this is an upstream spec gap, not
something introduced here.
- **Rate limits have not yet been measured against a live account.** The client
retries on `429` using whatever `Retry-After` GHL sends, but does not pre-emptively
throttle with an invented number, an assumed limit that's wrong would either
under-use the account or start failing calls that would have succeeded.
## Testing
```bash
npm test # unit tests, mocked fetch
npm run smoke # read only, against a live account
```
## Development
The tool layer is generated and must not be edited by hand:
```bash
npm run gen-naming # specification -> tool names
npm run gen-tools # specification -> zod schemas and call sites
npm run build
```
## Licence
MIT, see [LICENSE](LICENSE).
Built by [AIWerk](https://aiwerkmcp.com). Not affiliated with GoHighLevel / HighLevel Inc.
TDQS
Scored across 569 tools
Many tools are cleanly separated by domain prefixes, but there are numerous exact deprecated duplicates (e.g. agent_studio_get_agent_by_id vs _deprecated, saas_api_get_saas_plan vs _deprecated), and cross-module pairs such as locations_create_custom_field/custom_fields_create_custom_field and emails_fetch_campaigns/campaigns_get_campaigns have identical descriptions. This makes misselection likely despite the mostly clear prefixes.
Tool names overwhelmingly follow a domain_prefix_verb_noun snake_case pattern (calendars_create_calendar, payments_delete_coupon), which is predictable. There are blemishes like the undocumented text2pay verb, deprecated suffixes, and a few mismatched descriptions/names (products_list_invoices says 'List Products'), but the convention is broadly consistent.
At 569 tools, this is an extreme count for an MCP server and far exceeds the 50+ threshold. Even though it wraps a broad platform, the surface is too large for an agent to navigate reliably.
Coverage is extensive across CRM, invoicing, products, ad management, social media, and SaaS, with many full CRUD lifecycles. However, several modules are read-only or one-off (campaigns_get_campaigns, forms_get_forms, surveys_get_surveys, courses_import_courses), creating notable gaps for those domains.