Skip to main content
Glama
raiffhigor

Harpia Omnichannel MCP

by raiffhigor

Harpia Omnichannel MCP - versão produção

Projeto Node.js com núcleo desacoplado do canal de entrada, preparado para onion architecture e adapters plugáveis de canal, banco e LLM.

O que já vem pronto

  • Núcleo de aplicação independente do canal

  • Webhook de verificação da Meta

  • Reconhecimento de fala (audio recebido no WhatsApp)

  • Resposta por voz (audio enviado no WhatsApp, com fallback para texto)

  • Entrada HTTP genérica para qualquer origem

  • Validação da assinatura X-Hub-Signature-256

  • Estrutura multi-tenant

  • Repositório local em arquivo JSON para começar rápido

  • Adapter de dados mock para WMS/MCP

  • Adapter de dados PostgreSQL

  • Adapter de dados Oracle (edge-agent)

  • Adapter de dados via edge-agent remoto por tenant

  • Adapter de LLM compatível com OpenAI

  • Dockerfile e docker-compose

  • Logs simples em arquivo para rastreabilidade

Estrutura

packages/
  core-server/
    src/
      app.js
      server.js
  edge-agent/
    src/
      server.js
  shared/
    src/
      edge/
        operations.js
src/
  application/
  controllers/
  domain/
  infrastructure/
  interfaces/
  middlewares/
  repositories/
  routes/
  services/
  utils/
  app.js
  server.js
  edgeAgentServer.js
data/
  mcp-tools/
    BaseToolDefinition.js
    definitions/

Os arquivos em src/app.js, src/server.js e src/edgeAgentServer.js foram mantidos como shims de compatibilidade. Os runtimes distribuíveis agora vivem em packages/core-server e packages/edge-agent, com contrato comum em packages/shared.

Instalação

npm install
copy .env.example .env
npm start

Runtimes disponíveis:

npm run start:core
npm run start:edge-agent

Base de testes local

Para subir uma base local sem depender de tenant real, usuários reais ou credenciais reais:

npm run bootstrap:test-data

Isso popula o PostgreSQL local com a fixture em data/test-bootstrap.json, incluindo:

  • tenant tenant-teste-local

  • usuários OAuth claude.teste, chatgpt.teste e admin.teste

  • clients OAuth claude-local e chatgpt-local

  • perfis user_access para uso via MCP/HTTP

  • credencial MCP claude-mcp-local

Senhas padrão da fixture:

  • claude.teste -> claude123

  • chatgpt.teste -> chatgpt123

  • admin.teste -> admin123

Se quiser reaplicar automaticamente esses dados a cada startup local do core:

TEST_DATA_BOOTSTRAP_ENABLED=true
TEST_DATA_FIXTURE_FILE=./data/test-bootstrap.json
DATA_BACKEND=mock

Observação:

  • A fixture é pensada para ambiente local e usa backend mock no tenant de teste.

  • O bootstrap é idempotente: faz insert/update sem depender de base real pré-populada.

Modo desenvolvimento (hot-reload):

npm run dev
npm run dev:edge-agent

Desenvolvimento com Docker (sem rebuild a cada alteracao)

Use o compose de desenvolvimento para montar o codigo-fonte no container e aplicar alteracoes com hot-reload.

Subir ambiente dev:

docker compose -f docker-compose.dev.yml up -d --build

Ver logs:

docker compose -f docker-compose.dev.yml logs -f --tail=100

Parar ambiente dev:

docker compose -f docker-compose.dev.yml down

Observacoes:

  • O build inicial ainda e necessario para instalar dependencias.

  • Depois disso, alteracoes de codigo sao aplicadas sem rebuild da imagem.

  • O comando usado no container e npm run dev (node --watch).

Desenvolvimento do edge-agent sem rebuild:

docker compose -f packages/edge-agent/docker-compose.dev.yml up -d --build
docker compose -f packages/edge-agent/docker-compose.dev.yml logs -f --tail=100
docker compose -f packages/edge-agent/docker-compose.dev.yml down

Atualizacao rapida na VPS (producao)

No ambiente de producao (sem bind mount), o fluxo mais rapido e:

cd /caminho/do/projeto
git pull
docker compose up -d --build --no-deps harpia-whatsapp-mcp
docker compose ps
docker compose logs --tail=80 harpia-whatsapp-mcp

Esse comando reconstrói e reinicia somente o servico do core, sem derrubar dependencias desnecessariamente.

Deploy no Portainer (Server)

Arquivos prontos para stack:

  • docker-compose.portainer.yml

  • .env.portainer.example

Passos:

  1. Copie .env.portainer.example para .env e preencha tokens/chaves.

  2. No Portainer, crie uma Stack usando o conteúdo de docker-compose.portainer.yml.

  3. Garanta que a stack tenha acesso ao diretório data/ (bind ./data:/app/data).

  4. Faça deploy e valide em GET /health.

Observação:

  • Este compose é para o core-server no servidor.

  • O edge-agent roda no cliente e se conecta ao core via edge/poll e edge/result.

Para gerar artefatos standalone de deploy:

npm run export:runtimes

O comando gera:

  • dist/core-server: bundle para instalar no seu servidor

  • dist/edge-agent: bundle para instalar no cliente

Arquitetura

  • domain: regras puras de linguagem e intenção.

  • application: casos de uso independentes de canal.

  • infrastructure: adapters de banco, LLM, logging e canais de saída.

  • interfaces: adapters de entrada, como HTTP e webhook da Meta.

  • packages/core-server: runtime do servidor central.

  • packages/edge-agent: runtime local instalado no cliente.

  • packages/shared: contratos compartilhados entre core e agente.

O webhook do WhatsApp agora é apenas um adapter. O mesmo caso de uso pode ser acionado por HTTP, CLI, fila, e-mail ou outro canal futuro.

As operacoes usadas pelas tools sao carregadas a partir das ToolDefinitions persistentes em data/mcp-tools/definitions e refletidas no runtime sem depender de data/tools.json.

O provider usado pelo core continua sendo definido globalmente por DATA_BACKEND.

Cadastro de tenant

Os tenants ficam persistidos no PostgreSQL, na tabela tenants, e podem ser gerenciados pelo painel admin.

Exemplo:

[
  {
    "tenantId": "cliente-demo",
    "name": "Cliente Demo",
    "phoneNumberId": "123456789012345",
    "businessAccountId": "987654321",
    "accessToken": "EAAB...",
    "verifyToken": "opcional_por_tenant",
    "edgeAgent": {
      "mode": "reverse-client",
      "token": "token-compartilhado"
    },
    "orchestration": {
      "mode": "llm-mcp"
    },
    "llm": {
      "provider": "openai-compatible",
      "baseUrl": "https://api.openai.com/v1",
      "apiKey": "sk-...",
      "model": "gpt-4o-mini",
      "assistantName": "Ariane WMS",
      "assistantIdentity": "Eu sou a Ariane WMS, assistente operacional oficial da Cliente Demo.",
      "systemPrompt": "Sempre se identifique usando a mensagem oficial da empresa quando perguntarem sobre sua identidade.",
      "audio": {
        "provider": "elevenlabs",
        "baseUrl": "https://api.elevenlabs.io/v1",
        "apiKey": "elevenlabs-api-key",
        "voiceId": "JBFqnCBsd6RMkjVDRZzb",
        "modelId": "eleven_multilingual_v2",
        "outputFormat": "mp3_44100_128",
        "stability": 0.45,
        "similarityBoost": 0.8,
        "style": 0,
        "speakerBoost": true,
        "sttProvider": "openai-compatible",
        "sttBaseUrl": "https://api.openai.com/v1",
        "sttApiKey": "sk-...",
        "sttModel": "whisper-1"
      },
      "mcpOrchestratorEnabled": true,
      "mcpOrchestratorMaxToolRounds": 3
    },
    "wms": {
      "type": "mock",
      "oracleConnectString": "",
      "oracleUser": "",
      "oraclePassword": ""
    },
    "active": true
  }
]

Identidade e permissões de usuário

Para controlar quais usuários podem ver quais dados, use a tabela user_access no PostgreSQL.

Chave de identificação do usuário:

  • tenantId + channel + userId

Exemplos de userId por canal:

  • whatsapp: número/wa id (ex.: 5511999999999)

  • http: id interno do usuário

  • cli: id técnico de operador

Campos principais do perfil:

  • scopes: lista de escopos permitidos (receiving_status, stock_summary, picking_status)

  • maxInfoLevel: nível máximo de informação permitido (numérico)

  • branchIds: lista de filiais permitidas para o usuário

  • Formato recomendado: [{ "code": "SP01", "description": "Sao Paulo" }]

  • Compatibilidade: também aceita array de string (["SP01", "RJ02"])

  • active: habilita/desabilita o perfil

Variáveis:

  • USER_BRANCH_CONTEXT_TTL_MS (tempo para manter a filial selecionada na sessão do usuário)

O core valida o usuário antes de responder e bloqueia acessos sem perfil/permissão.

Fluxo para usuário com múltiplas filiais:

  1. Se o usuário tiver várias branchIds, o bot pede seleção da filial.

  2. O usuário responde com o código da filial (ex: SP01).

  3. O core mantém essa filial em contexto temporário por usuário/canal.

  4. Consultas seguintes usam essa filial até expirar o contexto.

URLs

  • Runtime dos adapters:

    • GET /api/runtime

  • Entrada genérica:

    • POST /api/interactions

  • Verificação Meta:

    • GET /meta/webhook

  • Recebimento de mensagens:

    • POST /meta/webhook

  • Health:

    • GET /health

  • MCP JSON-RPC:

    • POST /mcp

Fluxo

  1. Um canal de entrada envia a interação para o caso de uso.

  2. O caso de uso resolve tenant, intenção e provider.

  3. O adapter de dados consulta o backend configurado.

  4. O adapter de LLM pode complementar respostas livres.

  5. O adapter de saída devolve a resposta ao canal.

Exemplo de entrada genérica

curl -X POST http://localhost:3000/api/interactions \
  -H "Content-Type: application/json" \
  -d '{
    "tenantId": "cliente-demo",
    "channel": "http",
    "userId": "usuario-demo",
    "userName": "Operador",
    "text": "estoque"
  }'

Configuração de providers

  • DATA_BACKEND=mock: usa o adapter mock atual.

  • DATA_BACKEND=postgres: usa adapter PostgreSQL via pg.

  • DATA_BACKEND=oracle: usa adapter Oracle via oracledb.

  • DATA_BACKEND=edge-agent: cloud core consulta um edge-agent no cliente.

  • LLM_PROVIDER=disabled: desliga LLM.

  • LLM_PROVIDER=openai-compatible: usa qualquer endpoint compatível com OpenAI.

  • LLM_BASE_URL, LLM_API_KEY e LLM_MODEL: definem o provider de LLM.

  • AUDIO_PROVIDER, AUDIO_BASE_URL e AUDIO_API_KEY: permitem separar STT/TTS do provider de chat.

  • ELEVENLABS_VOICE_ID e ELEVENLABS_MODEL_ID: configuram TTS via ElevenLabs.

Configuração por tenant com fallback para .env:

  • Você pode definir na coluna config da tabela tenants, no bloco llm.

  • Campos suportados: provider, baseUrl, apiKey, model, systemPrompt, assistantName, assistantIdentity, mcpOrchestratorEnabled, mcpOrchestratorMaxToolRounds.

  • Se um campo não existir no tenant, o core usa o valor global do .env.

  • Para áudio, use llm.audio.provider, llm.audio.baseUrl, llm.audio.apiKey, llm.audio.sttProvider, llm.audio.sttBaseUrl, llm.audio.sttApiKey, llm.audio.sttModel, llm.audio.ttsModel, llm.audio.ttsVoice, llm.audio.ttsInstructions, llm.audio.voiceId, llm.audio.modelId, llm.audio.outputFormat, llm.audio.stability, llm.audio.similarityBoost, llm.audio.style, llm.audio.speakerBoost.

Campos de identidade do assistente por tenant:

  • assistantName: nome oficial do assistente da empresa.

  • assistantIdentity: texto oficial para responder quando o usuário perguntar "quem é você", "qual seu nome" ou perguntas equivalentes sobre a identidade do assistente.

Audio no WhatsApp

Para receber audio e responder por voz:

  • WA_VOICE_REPLY_ENABLED=true|false

  • STT_MODEL (default whisper-1)

  • TTS_MODEL (default tts-1)

  • TTS_VOICE (default alloy)

  • AUDIO_PROVIDER (default openai-compatible)

  • AUDIO_BASE_URL e AUDIO_API_KEY para separar o provider de audio do provider de chat

  • AUDIO_STT_PROVIDER, AUDIO_STT_BASE_URL e AUDIO_STT_API_KEY para STT separado do TTS

  • ELEVENLABS_VOICE_ID, ELEVENLABS_MODEL_ID, ELEVENLABS_OUTPUT_FORMAT

Observação: a transcrição e a síntese usam LLM_BASE_URL e LLM_API_KEY em endpoints compatíveis com OpenAI (/audio/transcriptions e /audio/speech). Se o LLM_BASE_URL apontar para LiteLLM, configure aliases de áudio como harpia-stt e harpia-tts no litellm-config.yaml e use esses nomes em STT_MODEL e TTS_MODEL. Se AUDIO_BASE_URL estiver configurado, STT/TTS usam esse endpoint em vez de compartilhar o provider de chat. Para AUDIO_PROVIDER=elevenlabs, apenas o TTS vai para ElevenLabs; o STT continua via provider configurado em AUDIO_STT_PROVIDER/AUDIO_STT_BASE_URL.

Exemplo de .env com ElevenLabs para voz e OpenAI para transcricao:

AUDIO_PROVIDER=elevenlabs
AUDIO_API_KEY=elevenlabs-api-key
ELEVENLABS_VOICE_ID=JBFqnCBsd6RMkjVDRZzb
ELEVENLABS_MODEL_ID=eleven_multilingual_v2
ELEVENLABS_OUTPUT_FORMAT=mp3_44100_128
AUDIO_STT_PROVIDER=openai-compatible
AUDIO_STT_BASE_URL=https://api.openai.com/v1
AUDIO_STT_API_KEY=sk-...
AUDIO_STT_MODEL=whisper-1

Provider Edge Agent (cloud core)

No modo atual (reverso), o core não abre conexão para o cliente. O edge-agent do cliente faz long-poll no core e recebe comandos.

Defina no core:

  • EDGE_AGENT_SHARED_TOKEN

  • EDGE_AGENT_TIMEOUT_MS

  • EDGE_POLL_WAIT_MS

Recomendado por tenant no campo config.edgeAgent da tabela tenants:

  • edgeAgent.token

MCP no Core

O core agora expõe um endpoint MCP (JSON-RPC 2.0) para LLMs de terceiros consumirem tools operacionais.

Endpoint:

  • POST /mcp

Autenticação opcional:

  • MCP_SERVER_TOKEN

  • Envie em Authorization: Bearer <token> ou x-mcp-token

Autenticação recomendada para cliente externo:

  • As credenciais por cliente/integrador ficam na tabela mcp_clients.

  • Cada credencial pode definir:

    • id

    • name

    • token

    • allowedTenantIds

    • active

  • Quando allowedTenantIds contiver * ou estiver ausente/vazio, a credencial pode acessar qualquer tenant.

  • Quando allowedTenantIds listar tenants explicitamente, tools/call rejeita chamadas fora dessa allowlist.

  • MCP_SERVER_TOKEN continua válido como fallback global, sem escopo por tenant.

  • Guia de entrega para cliente: docs/mcp-client-onboarding.md

Ativação de tools:

  • As tools sao sincronizadas a partir das ToolDefinitions persistentes e do catalogo mcp_tools no banco.

  • Defina active: true|false na ToolDefinition persistente ou no catalogo do banco.

  • tools/list retorna apenas tools ativas.

  • Tools inativas são rejeitadas em tools/call.

  • tools/call valida os argumentos contra o inputSchema de cada tool e rejeita campos extras.

ToolDefinitions persistentes (data/mcp-tools/definitions):

  • Permitem criar/alterar tools sem editar mcpToolPolicyService.js.

  • Cada arquivo exporta uma ToolDefinition com name, operation, active, description, inputSchema, labels e units.

  • labels e units sao opcionais e alimentam o formatter de resultado.

  • Bootstrap inicial da pasta persistente: npm run tools:bootstrap.

Métodos suportados:

  • initialize

  • tools/list

  • tools/call

Tools disponíveis:

  • get_inbound_status (args: tenantId, branchId?, userId?, channel?, traceId?)

  • get_inbound_products (args: tenantId, branchId?, protocolId?, userId?, channel?, traceId?)

  • get_stock_summary (args: tenantId, branchId?, userId?, channel?, traceId?)

  • get_picking_status (args: tenantId, branchId?, userId?, channel?, traceId?)

  • get_orders_flow_by_date (args: tenantId, branchId?, targetDate?, initialDate?, finalDate?, userId?, channel?, traceId?)

  • get_billed_orders_count (args: tenantId, branchId?, targetDate?, initialDate?, finalDate?, userId?, channel?, traceId?)

Formato padrao de retorno das tools:

  • schemaVersion

  • toolName

  • tenantId

  • branchId

  • metricas (valores numericos)

  • labels (descricao semantica de cada metrica)

  • units (unidade de medida por metrica)

Orquestração WhatsApp -> LLM -> MCP

Opcionalmente, no canal WhatsApp, você pode delegar a resposta para a LLM com uso de tools MCP.

Variáveis:

  • LLM_MCP_ORCHESTRATOR_ENABLED=true|false

  • LLM_MCP_ORCHESTRATOR_MAX_TOOL_ROUNDS (default 3)

  • ORCHESTRATION_MODE_DEFAULT=rule-engine|llm-basic|llm-mcp

Configuração por tenant em data/tenants.json:

  • orchestration.mode=rule-engine|llm-basic|llm-mcp

Precedência de modo:

  1. tenant.orchestration.mode

  2. ORCHESTRATION_MODE_DEFAULT

  3. fallback compatível: se LLM_MCP_ORCHESTRATOR_ENABLED=true no WhatsApp, usa llm-mcp

  4. padrão final: rule-engine

Quando habilitado:

  1. mensagem entra no WhatsApp

  2. LLM decide e chama apenas as tools ativas (get_inbound_status, get_inbound_products, get_stock_summary, get_picking_status, get_orders_flow_by_date, get_billed_orders_count)

  3. core executa tool por tenant e devolve para a LLM

  4. resposta final padronizada volta ao WhatsApp

Provider PostgreSQL

Defina POSTGRES_URL ou os campos POSTGRES_HOST, POSTGRES_DB, POSTGRES_USER, POSTGRES_PASSWORD.

As consultas padrão assumem tabelas:

  • inbound_tasks

  • stock_daily_summary

  • picking_orders

Você pode sobrescrever qualquer consulta com:

  • POSTGRES_QUERY_INBOUND

  • POSTGRES_QUERY_INBOUND_PRODUCTS

  • POSTGRES_QUERY_STOCK

  • POSTGRES_QUERY_PICKING

  • POSTGRES_QUERY_ORDERS_FLOW_BY_DATE

  • POSTGRES_QUERY_BILLED_ORDERS_COUNT

Provider Oracle

Para Oracle, instale a dependencia:

npm install oracledb

Defina ORACLE_CONNECT_STRING (ou ORACLE_HOST, ORACLE_PORT, ORACLE_SERVICE_NAME) e credenciais ORACLE_USER e ORACLE_PASSWORD.

Você pode sobrescrever qualquer consulta com:

  • ORACLE_QUERY_INBOUND

  • ORACLE_QUERY_INBOUND_PRODUCTS

  • ORACLE_QUERY_STOCK

  • ORACLE_QUERY_PICKING

  • ORACLE_QUERY_ORDERS_FLOW_BY_DATE

  • ORACLE_QUERY_BILLED_ORDERS_COUNT

As consultas padrao assumem tabelas:

  • inbound_tasks

  • stock_daily_summary

  • picking_orders

  • orders

Voce pode sobrescrever qualquer consulta com:

  • ORACLE_QUERY_INBOUND

  • ORACLE_QUERY_STOCK

  • ORACLE_QUERY_PICKING

  • ORACLE_QUERY_ORDERS_FLOW_BY_DATE

  • ORACLE_QUERY_BILLED_ORDERS_COUNT

Entrada por CLI

npm run chat

Parâmetros opcionais:

npm run chat -- --tenant cliente-demo --user operador-01 --name "Carlos"

Edge Agent local (cliente)

Suba um processo separado no ambiente do cliente:

npm run start:edge-agent

Modo recomendado (EDGE_AGENT_MODE=reverse-client):

  • o cliente conecta no core em EDGE_CORE_URL

  • faz poll em POST /edge/poll

  • responde em POST /edge/result

  • executa queries no backend local (EDGE_DATA_BACKEND=postgres|oracle|mock)

  • resolve operacoes dinamicamente por convencao (snake_case -> getPascalCase) para reduzir manutencao no server do agente

Variáveis do edge-agent local:

  • EDGE_AGENT_MODE=reverse-client

  • EDGE_CORE_URL=http://host-do-core:3000

  • EDGE_AGENT_TENANT_ID=cliente-demo

  • EDGE_AGENT_TOKEN (ou EDGE_AGENT_SHARED_TOKEN)

  • EDGE_AGENT_HTTP_TIMEOUT_MS

  • EDGE_AGENT_RECONNECT_DELAY_MS

  • EDGE_AGENT_CONCURRENCY (quantidade de comandos processados em paralelo)

  • EDGE_AGENT_MAX_QUEUE_SIZE (limite da fila local antes de rejeitar novos comandos)

Modo legado opcional (EDGE_AGENT_MODE=legacy-server):

  • mantém rotas GET /health e POST /agent/query para core conectar direto no cliente.

Comandos já suportados

  • recebimento

  • estoque

  • separacao

  • minhas filiais

  • trocar filial

  • ajuda

Próximo passo recomendado

Trocar o adapter mockOperationsDataProvider.js por adapters reais de Oracle, MCP, PostgreSQL ou outro backend, sem alterar o caso de uso central.

F
license - not found
-
quality - not tested
-
maintenance - not tested

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/raiffhigor/harpia-whatsapp-mcp-production'

If you have feedback or need assistance with the MCP directory API, please join our Discord server