MCP Hub
Back to servers

Harpia Omnichannel MCP

Um servidor MCP omnichannel que integra WhatsApp, LLM, bancos de dados e edge agents, permitindo orquestração de ferramentas e respostas de voz.

glama
Updated
Apr 24, 2026

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.

Reviews

No reviews yet

Sign in to write a review