Skip to main content
Glama
jamersoncalixto

ghl-mcp-remote

ghl-mcp-remote

Remote MCP (Model Context Protocol) server for GoHighLevel — multi-tenant, accessible via URL, to be used from Claude or ChatGPT by any agency, without each one needing to run anything locally.

This is a separate project from the original ghl-mcp (stdio, personal/local use). Neither one depends on the other.

Difference from the original ghl-mcp

ghl-mcp (original)

ghl-mcp-remote (this one)

Transport

stdio (local process)

HTTP (POST /mcp), hostable

Tenants

1 agency per installation, credentials in ~/.ghl-mcp/credentials.json

Any number of agencies, isolated by companyId, credentials in Postgres

"Login"

npm run auth in the terminal

GHL's own authorization screen, triggered by Claude/ChatGPT

Use

You, locally

Any company, from Claude.ai/ChatGPT, via URL

The business code (the tools in src/tools/) is practically identical in both — only the authentication/storage layer changes.

Related MCP server: GoHighLevel MCP Server

Architecture

Claude/ChatGPT ──(1) descobre──> GET /.well-known/oauth-authorization-server
               ──(2) registra───> POST /register                      (DCR, automático)
               ──(3) pede login─> GET /authorize ──redirect──> tela da GHL (o "login")
                                                        <──redirect── GET /oauth/ghl/callback
               <──code+state───── (nosso próprio código de autorização)
               ──(4) troca──────> POST /token ──> access_token + refresh_token nossos
               ──(5) chama tool─> POST /mcp  (Authorization: Bearer <access_token>)
  • "Login" = authorize GHL. This service has no account/password of its own. When an agency admin approves access on GHL's own screen, that already creates/updates their tenant (identified by GHL's companyId) and completes the login on the MCP side.

  • A single GHL Marketplace app (same GHL_CLIENT_ID/GHL_CLIENT_SECRET) serves any agency that installs it — there's no need to create an app per client.

  • Each tool call arrives authenticated with a Bearer token issued by this server; the middleware resolves that token to the correct companyId and injects it into an AsyncLocalStorage (src/tenant-context.ts) — that's how the tools code (identical to the original project's) remains "unaware" of multi-tenancy.

  • Built on top of what @modelcontextprotocol/sdk itself already provides for OAuth servers (server/auth/router.ts, provider.ts) — see src/auth/mcp-oauth-provider.ts.

Prerequisites to run anywhere

  1. OAuth app in GHL Marketplace (Developer > your app), "Agency" or "Agency & Sub-Account" distribution:

    • Registered redirect URI: <PUBLIC_URL>/oauth/ghl/callback (must be the final public URL of this service — HTTPS).

    • Scopes: the same ones listed in src/services/scopes.ts.

  2. Postgres (any one — Supabase, Neon, RDS, the hosting platform's own managed Postgres, etc.). Run db/schema.sql on it once.

  3. Node.js 20+ (or this project's Docker image, which already includes it).

Environment variables

See .env.example. Summary:

Variable

Description

GHL_CLIENT_ID / GHL_CLIENT_SECRET

From the GHL Marketplace OAuth app

PUBLIC_URL

Final public URL of this service, no trailing slash

PORT

Port the process listens on (many platforms override it themselves)

DATABASE_URL

Postgres connection string

TOKEN_ENCRYPTION_KEY

32 bytes in base64 — openssl rand -base64 32

Run locally (dev)

npm install
npm run build
npm start

Checks possible without any public domain:

curl localhost:8080/healthz
curl localhost:8080/.well-known/oauth-authorization-server

The full OAuth flow (actually authorizing in GHL, getting a token, calling a tool) only works with a real PUBLIC_URL (HTTPS) up, because GHL needs to be able to redirect the agency admin's browser back here — and that same URL needs to be registered as a redirect URI in the GHL app.

Deploy

This project doesn't assume any specific hosting platform — it just includes a generic Dockerfile. Any platform that runs a Docker image (or node dist/index.js directly) works, as long as:

  1. It exposes a stable public HTTPS URL → that becomes PUBLIC_URL.

  2. It injects the environment variables from the table above.

  3. The Postgres pointed to by DATABASE_URL has already run db/schema.sql.

  4. The GHL Marketplace app's redirect URI is updated to <PUBLIC_URL>/oauth/ghl/callback as soon as the final URL is known.

Connect in Claude / ChatGPT

Once hosted:

  • Claude.ai / Claude Desktop: Settings → Connectors → Add custom connector → URL: https://<seu-dominio>/mcp. Claude will take you through the authorization flow automatically.

  • ChatGPT: in workspaces with support for remote Connectors/MCP (varies by plan — Team, Enterprise, or "Developer mode"), add a connector pointing to https://<seu-dominio>/mcp.

Caveat about ChatGPT: support for remote MCP connectors with OAuth in ChatGPT varies by plan/workspace, and some surfaces (e.g. Deep Research) restrict which tool formats they accept (sometimes only tools in the "search"/"fetch" format). This server follows the MCP authorization spec to the letter (the same one Claude uses), which maximizes compatibility — but it's worth actually testing once it's hosted, since behavior on the ChatGPT side is beyond our control.

Structure

src/
  index.ts                 App Express: monta o router de OAuth, POST/GET/DELETE /mcp,
                            GET /oauth/ghl/callback, GET /healthz, CORS.
  server.ts                 createMcpServer() — registra as tools (idêntico ao projeto original).
  tenant-context.ts          AsyncLocalStorage que carrega o companyId durante cada request.
  db/
    pool.ts                  Pool do `pg` a partir de DATABASE_URL.
    crypto.ts                 AES-256-GCM (tokens da GHL em repouso) + SHA-256 (hash dos nossos tokens).
    agencies.ts                Tokens de agência da GHL por companyId (substitui o antigo token-store.ts).
    oauth-store.ts              Clients MCP, pending auth, authorization codes, access/refresh tokens.
  auth/
    ghl-oauth.ts               Troca/refresh de tokens com a GHL — equivalente ao oauth-flow.ts original,
                               mas web-based e por tenant em vez de CLI + arquivo único.
    location-tokens.ts          Cache de location tokens, agora chaveado por companyId.
    mcp-oauth-provider.ts        Implementa OAuthServerProvider do SDK — o núcleo do "login = autorizar a GHL".
    ghl-callback.ts               Handler de GET /oauth/ghl/callback.
  services/
    constants.ts, scopes.ts, ghl-client.ts   Idênticos ao projeto original (só o import de token mudou).
  tools/
    *.ts                       Idênticos ao projeto original, exceto locations.ts (cache agora por tenant).
db/
  schema.sql                  DDL do Postgres — rodar uma vez antes do primeiro start.

Security

  • GHL refresh tokens: encrypted at rest (AES-256-GCM).

  • Access/refresh tokens that this server issues to Claude/ChatGPT: stored only as SHA-256 hashes — never in plain text, like passwords.

  • PKCE (S256) required throughout the MCP-side flow, validated locally (not delegated to GHL).

  • No agency's credentials are accessible from another agency's token — all Postgres access is filtered by companyId, and that value only comes into play after the Bearer token is validated.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    B
    quality
    D
    maintenance
    MCP server for GoHighLevel API v2 that provides 50+ tools for CRM, billing, marketing, and operations workflows, enabling natural language interaction with contacts, opportunities, conversations, and more.
    50
    1
    MIT
  • F
    license
    Not graded
    quality
    D
    maintenance
    MCP server for GoHighLevel sub-accounts, enabling management of CRM contacts, pipelines, calendars, invoices, and more via natural language.
    -