ghl-mcp-remote
ghl-mcp-remote
Servidor MCP (Model Context Protocol) remoto para GoHighLevel — multi-tenant, acessível via URL, para ser usado a partir do Claude ou do ChatGPT por qualquer agência, sem que cada uma precise rodar nada localmente.
Este é um projeto separado do ghl-mcp original (stdio, uso pessoal/local).
Nenhum dos dois depende do outro.
Diferença para o ghl-mcp original
|
| |
Transporte | stdio (processo local) | HTTP ( |
Tenants | 1 agência por instalação, credenciais em | Qualquer nº de agências, isoladas por |
"Login" |
| Tela de autorização da própria GHL, disparada pelo Claude/ChatGPT |
Uso | Você, localmente | Qualquer empresa, a partir do Claude.ai/ChatGPT, via URL |
O código de negócio (as tools em src/tools/) é praticamente idêntico nos dois — só a camada
de autenticação/armazenamento muda.
Arquitetura
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" = autorizar a GHL. Não existe conta/senha própria deste serviço. Quando o admin de uma agência aprova o acesso na tela da própria GHL, isso já cria/atualiza o tenant dele (identificado pelo
companyIdda GHL) e completa o login do lado do MCP.Um único app GHL Marketplace (mesmo
GHL_CLIENT_ID/GHL_CLIENT_SECRET) atende qualquer agência que o instalar — não é preciso criar um app por cliente.Cada chamada de tool chega autenticada com um Bearer token emitido por este servidor; o middleware resolve esse token pro
companyIdcerto e injeta isso numAsyncLocalStorage(src/tenant-context.ts) — é assim que o código das tools (idêntico ao do projeto original) permanece "sem saber" de multi-tenancy.Implementado em cima do que o próprio
@modelcontextprotocol/sdkjá traz para servidores OAuth (server/auth/router.ts,provider.ts) — versrc/auth/mcp-oauth-provider.ts.
Pré-requisitos pra rodar em qualquer lugar
App OAuth no GHL Marketplace (Developer > seu app), distribuição "Agency" ou "Agency & Sub-Account":
Redirect URI cadastrada:
<PUBLIC_URL>/oauth/ghl/callback(precisa ser a URL pública final deste serviço — HTTPS).Scopes: os mesmos listados em
src/services/scopes.ts.
Postgres (qualquer um — Supabase, Neon, RDS, o Postgres gerenciado da própria plataforma de hosting, etc.). Rodar
db/schema.sqlnele uma vez.Node.js 20+ (ou a imagem Docker deste projeto, que já inclui isso).
Variáveis de ambiente
Ver .env.example. Resumo:
Variável | Descrição |
| Do app OAuth do GHL Marketplace |
| URL pública final deste serviço, sem barra no final |
| Porta em que o processo escuta (muitas plataformas sobrescrevem sozinhas) |
| Connection string do Postgres |
| 32 bytes em base64 — |
Rodar localmente (dev)
npm install
npm run build
npm startVerificações possíveis sem nenhum domínio público:
curl localhost:8080/healthz
curl localhost:8080/.well-known/oauth-authorization-serverO fluxo completo de OAuth (autorizar de verdade na GHL, ganhar token, chamar uma tool) só
funciona com uma PUBLIC_URL real (HTTPS) no ar, porque a GHL precisa conseguir redirecionar
o navegador do admin da agência de volta pra cá — e essa mesma URL precisa estar cadastrada
como redirect URI no app GHL.
Deploy
Este projeto não assume nenhuma plataforma de hosting específica — só inclui um
Dockerfile genérico. Qualquer plataforma que rode uma imagem Docker (ou node dist/index.js
direto) serve, desde que:
Exponha uma URL pública HTTPS estável → isso vira
PUBLIC_URL.Injete as variáveis de ambiente da tabela acima.
O Postgres apontado por
DATABASE_URLjá tenha rodadodb/schema.sql.O redirect URI do app GHL Marketplace seja atualizado pra
<PUBLIC_URL>/oauth/ghl/callbackassim que a URL final for conhecida.
Conectar no Claude / ChatGPT
Depois de hospedado:
Claude.ai / Claude Desktop: Configurações → Connectors → Add custom connector → URL:
https://<seu-dominio>/mcp. O Claude vai te levar pro fluxo de autorização automaticamente.ChatGPT: em workspaces com suporte a Connectors/MCP remoto (varia por plano — Team, Enterprise, ou "Developer mode"), adicionar um connector apontando pra
https://<seu-dominio>/mcp.
Caveat sobre o ChatGPT: o suporte a conectores MCP remotos com OAuth no ChatGPT varia por plano/workspace, e algumas superfícies (ex. Deep Research) restringem quais formatos de tool aceitam (às vezes só tools no formato "search"/"fetch"). Este servidor segue a spec de autorização do MCP à risca (a mesma que o Claude usa), o que maximiza compatibilidade — mas vale testar de verdade assim que estiver hospedado, já que o comportamento do lado do ChatGPT foge do nosso controle.
Estrutura
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.Segurança
Refresh tokens da GHL: criptografados em repouso (AES-256-GCM).
Access/refresh tokens que este servidor emite para Claude/ChatGPT: guardados só como hash SHA-256 — nunca em texto puro, igual senha.
PKCE (S256) obrigatório em todo o fluxo MCP-side, validado localmente (não delegado à GHL).
Nenhuma credencial de uma agência é acessível a partir do token de outra — todo acesso ao Postgres é filtrado por
companyId, e esse valor só entra em cena depois que o Bearer token é validado.