@nectarcrm/mcp
v26.3.2
Published
Servidor MCP do NectarCRM — busca contatos, oportunidades, tarefas e funis a partir de qualquer cliente MCP
Maintainers
Readme
@nectarcrm/mcp
Servidor MCP do NectarCRM. Traduz chamadas MCP para a API v2 e devolve o resultado — nada além disso.
Dois modos
| Modo | Como roda | Autenticação | Para quem |
|---|---|---|---|
| stdio (nectar-mcp) | na máquina de quem usa, via npx | OAuth pelo navegador (nectar-mcp login), credencial salva na máquina | uso individual (Claude Code, Cursor e outros clientes locais) |
| HTTP remoto (npm start) | serviço hospedado pela Nectar | OAuth 2.1 por usuário | abrir para clientes externos (Claude web, ChatGPT) |
A garantia é a mesma nos dois: o token é do usuário e quem autoriza cada leitura é a API v2. O que muda é de onde o token vem.
Uso rápido (stdio)
- Autorize no navegador (uma vez por computador):
NECTAR_API_URL="https://SEU-AMBIENTE/crm/api/2" NECTAR_CLIENT_ID="ncrm_mcp" npx -y @nectarcrm/mcp loginA credencial fica em ~/.nectar-mcp/credentials.json (permissão 0600) e é renovada sozinha. A conexão aparece em Configurações › Integrações › IA, de onde pode ser desconectada. Token de API comum é recusado: o acesso do agente precisa de escopo, prazo e um lugar onde ser revogado.
- No
~/.claude/settings.json(ou no arquivo de MCP do seu assistente):
{
"mcpServers": {
"nectar": {
"command": "npx",
"args": ["-y", "@nectarcrm/mcp"]
}
}
}npx @nectarcrm/mcp status mostra a credencial em uso; npx @nectarcrm/mcp logout apaga.
Depois é só perguntar em linguagem natural: "quais oportunidades estão paradas há mais de 15 dias?", "resume o histórico da Acme", "o que está atrasado pra mim?".
Contrato e critérios de aceite: nectarcrm3/docs/plans/SPEC_CRM_AGENTS.md.
Decisões de implementação: nectarcrm3/docs/plans/DESIGN_FASE0_FASE1_CRM_AGENTS.md.
O que este serviço não faz
Estas não são omissões da versão atual, são limites permanentes do desenho:
- não acessa banco — não há driver no
package.json, e um teste de arquitetura falha se alguém adicionar; - não guarda estado — nem sessão, nem token, nem resposta entre requests;
- não decide permissão — quem autoriza é a API v2, com o Bearer do usuário que conectou a conta;
- não chama LLM nem planeja passos pelo agente;
- não monta URL a partir de entrada do modelo — todo caminho vem de tabela estática, e só
src/upstream/v2client.tsfazfetch.
O motivo é o raio de explosão: se este serviço for comprometido, o atacante ganha um tradutor de protocolo, não os dados de todos os tenants.
Desenvolvimento
Modo stdio, direto do repositório:
npm install
NECTAR_API_URL="https://SEU-AMBIENTE/crm/api/2" NECTAR_CLIENT_ID="ncrm_mcp" npx tsx src/cli.ts login
npm run dev:stdioModo HTTP remoto:
export NECTAR_V2_BASE_URL="http://localhost:8080/crm/api/2"
export MCP_RESOURCE_URI="https://mcp.nectarcrm.com.br"
export MCP_RESOURCE_METADATA_URL="https://mcp.nectarcrm.com.br/.well-known/oauth-protected-resource"
export PORT=8791
npm run devConferir que subiu:
curl -s localhost:8791/healthO 401 tem de trazer o desafio que ensina o cliente a se autenticar:
curl -si -X POST localhost:8791/mcp -H 'Content-Type: application/json' -d '{}' | grep -i www-authenticateVariáveis
Modo stdio:
| Variável | Obrigatória | Para quê |
|---|---|---|
| NECTAR_API_URL | sim | URL da API v2, ex.: https://app.nectarcrm.com.br/crm/api/2 |
| NECTAR_API_TOKEN | sim | token do seu usuário; as buscas respeitam exatamente as suas permissões |
| MCP_ENABLED_TOOLS | não | allowlist separada por vírgula |
Modo HTTP remoto:
| Variável | Obrigatória | Para quê |
|---|---|---|
| NECTAR_V2_BASE_URL | sim | destino interno fixo da API v2 |
| MCP_RESOURCE_URI | sim | identificador do recurso protegido, usado no desafio 401 |
| MCP_RESOURCE_METADATA_URL | sim | URL da metadata RFC 9728 devolvida no WWW-Authenticate |
| NECTAR_CHANNEL_KEY | não | chave do canal interno; sem ela o CRM descarta os headers X-Nectar-* |
| MCP_ENABLED_TOOLS | não | allowlist separada por vírgula; permite cortar uma tool sem deploy |
| MCP_V2_MAX_CONCURRENT | não | chamadas simultâneas à v2 por processo; acima disso o gateway recusa na hora. Padrão 50 |
| GRAYLOG_HOST | não | liga o envio dos logs ao Graylog (GELF/UDP). Sem ela, os logs ficam só no stdout, que o Graylog não coleta |
| GRAYLOG_PORT | não | padrão 12201 |
| GRAYLOG_LEVEL | não | error, warn, info ou debug; padrão info |
| GRAYLOG_FACILITY | não | padrão nectar-mcp |
| PORT | não | padrão 8080 |
Falta de variável obrigatória derruba o start de propósito: um default silencioso apontando para produção é a forma mais fácil de misturar ambientes.
Tools
| Tool | Escopo | Endpoint v2 |
|---|---|---|
| nectar_search_contacts | contacts:read | GET /contacts |
| nectar_search_opportunities | opportunities:read | GET /opportunities |
| nectar_search_tasks | tasks:read | GET /tasks |
| nectar_list_pipelines | pipelines:read | GET /pipelines |
| nectar_list_custom_fields | custom-fields:read | GET /custom-fields |
| nectar_list_users | users:read | GET /users |
| nectar_whoami | users:read | GET /users/{id} (id do próprio token) e GET /health |
Faltam três tools previstas na SPEC, e por um motivo específico: os endpoints que elas usam ainda não existem na v2 (/opportunities/summary, /contacts/{id}/timeline, /search). Elas entram junto com esses endpoints; o gateway não vai agregar páginas para simular agregação.
Testes
npm testDois grupos, com propósitos diferentes:
test/traducao.test.ts— argumento da tool → query param da v2, com a v2 dublada por um servidor local. É o teste mais importante daqui: se o gateway mandar um parâmetro com nome errado, a v2 ignora o desconhecido e devolve a lista sem o filtro. O agente recebe "todas as oportunidades" quando pediu "as paradas", e a resposta parece certa.test/arquitetura.test.ts— sustenta as invariantes: sem driver de banco,fetchem um arquivo só, descoberta determinística, nenhuma tool aceitandotenant.
Pendências conhecidas
src/routing/enums.tstem enums cujos inteiros ainda não foram conferidos contra as constantes do CRM. Enquanto estiverem marcados, o filtro correspondente não deve ser publicado — um chute aqui produz resposta errada com cara de certa.- OAuth ainda não está ligado de ponta a ponta: o piloto local usa um Bearer obtido à mão. O fluxo completo depende do PKCE (já no CRM) e do roteamento de
/.well-known/oauth-*no edge.
