Harpia Omnichannel MCP
Offers Docker and docker-compose configurations for deployment, including development with hot-reload.
Provides text-to-speech synthesis for voice responses in WhatsApp, with configurable voice and model settings.
Handles webhook verification and message reception from Meta's WhatsApp Business API.
Integrates OpenAI-compatible LLM for processing user intents and generating responses, with optional speech-to-text via Whisper.
Provides a docker-compose file for deploying the core server via Portainer stack.
Data backend adapter for PostgreSQL, storing tenants, users, and access permissions.
Supports sending and receiving WhatsApp messages, including audio transcription and voice reply via ElevenLabs.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Harpia Omnichannel MCPEnviar mensagem de voz para o cliente sobre o status do pedido #1234"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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-256Estrutura 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 startRuntimes disponíveis:
npm run start:core
npm run start:edge-agentBase de testes local
Para subir uma base local sem depender de tenant real, usuários reais ou credenciais reais:
npm run bootstrap:test-dataIsso popula o PostgreSQL local com a fixture em data/test-bootstrap.json, incluindo:
tenant
tenant-teste-localusuários OAuth
claude.teste,chatgpt.testeeadmin.testeclients OAuth
claude-localechatgpt-localperfis
user_accesspara uso via MCP/HTTPcredencial MCP
claude-mcp-local
Senhas padrão da fixture:
claude.teste->claude123chatgpt.teste->chatgpt123admin.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=mockObservação:
A fixture é pensada para ambiente local e usa backend
mockno 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-agentDesenvolvimento 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 --buildVer logs:
docker compose -f docker-compose.dev.yml logs -f --tail=100Parar ambiente dev:
docker compose -f docker-compose.dev.yml downObservacoes:
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 downAtualizacao 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-mcpEsse 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:
Copie
.env.portainer.examplepara.enve preencha tokens/chaves.No Portainer, crie uma Stack usando o conteúdo de
docker-compose.portainer.yml.Garanta que a stack tenha acesso ao diretório
data/(bind./data:/app/data).Faça deploy e valide em
GET /health.
Observação:
Este compose é para o
core-serverno servidor.O
edge-agentroda no cliente e se conecta ao core viaedge/polleedge/result.
Para gerar artefatos standalone de deploy:
npm run export:runtimesO comando gera:
dist/core-server: bundle para instalar no seu servidordist/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áriocli: 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árioFormato 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:
Se o usuário tiver várias
branchIds, o bot pede seleção da filial.O usuário responde com o código da filial (ex:
SP01).O core mantém essa filial em contexto temporário por usuário/canal.
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
Um canal de entrada envia a interação para o caso de uso.
O caso de uso resolve tenant, intenção e provider.
O adapter de dados consulta o backend configurado.
O adapter de LLM pode complementar respostas livres.
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 viapg.DATA_BACKEND=oracle: usa adapter Oracle viaoracledb.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_KEYeLLM_MODEL: definem o provider de LLM.AUDIO_PROVIDER,AUDIO_BASE_URLeAUDIO_API_KEY: permitem separar STT/TTS do provider de chat.ELEVENLABS_VOICE_IDeELEVENLABS_MODEL_ID: configuram TTS via ElevenLabs.
Configuração por tenant com fallback para .env:
Você pode definir na coluna
configda tabelatenants, no blocollm.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|falseSTT_MODEL(defaultwhisper-1)TTS_MODEL(defaulttts-1)TTS_VOICE(defaultalloy)AUDIO_PROVIDER(defaultopenai-compatible)AUDIO_BASE_URLeAUDIO_API_KEYpara separar o provider de audio do provider de chatAUDIO_STT_PROVIDER,AUDIO_STT_BASE_URLeAUDIO_STT_API_KEYpara STT separado do TTSELEVENLABS_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-1Provider 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_TOKENEDGE_AGENT_TIMEOUT_MSEDGE_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_TOKENEnvie em
Authorization: Bearer <token>oux-mcp-token
Autenticação recomendada para cliente externo:
As credenciais por cliente/integrador ficam na tabela
mcp_clients.Cada credencial pode definir:
idnametokenallowedTenantIdsactive
Quando
allowedTenantIdscontiver*ou estiver ausente/vazio, a credencial pode acessar qualquer tenant.Quando
allowedTenantIdslistar tenants explicitamente,tools/callrejeita chamadas fora dessa allowlist.MCP_SERVER_TOKENcontinua 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_toolsno banco.Defina
active: true|falsena ToolDefinition persistente ou no catalogo do banco.tools/listretorna apenas tools ativas.Tools inativas são rejeitadas em
tools/call.tools/callvalida os argumentos contra oinputSchemade 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,labelseunits.labelseunitssao opcionais e alimentam o formatter de resultado.Bootstrap inicial da pasta persistente:
npm run tools:bootstrap.
Métodos suportados:
initializetools/listtools/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:
schemaVersiontoolNametenantIdbranchIdmetricas(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|falseLLM_MCP_ORCHESTRATOR_MAX_TOOL_ROUNDS(default3)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:
tenant.orchestration.modeORCHESTRATION_MODE_DEFAULTfallback compatível: se
LLM_MCP_ORCHESTRATOR_ENABLED=trueno WhatsApp, usallm-mcppadrão final:
rule-engine
Quando habilitado:
mensagem entra no WhatsApp
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)core executa tool por tenant e devolve para a LLM
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_tasksstock_daily_summarypicking_orders
Você pode sobrescrever qualquer consulta com:
POSTGRES_QUERY_INBOUNDPOSTGRES_QUERY_INBOUND_PRODUCTSPOSTGRES_QUERY_STOCKPOSTGRES_QUERY_PICKINGPOSTGRES_QUERY_ORDERS_FLOW_BY_DATEPOSTGRES_QUERY_BILLED_ORDERS_COUNT
Provider Oracle
Para Oracle, instale a dependencia:
npm install oracledbDefina 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_INBOUNDORACLE_QUERY_INBOUND_PRODUCTSORACLE_QUERY_STOCKORACLE_QUERY_PICKINGORACLE_QUERY_ORDERS_FLOW_BY_DATEORACLE_QUERY_BILLED_ORDERS_COUNT
As consultas padrao assumem tabelas:
inbound_tasksstock_daily_summarypicking_ordersorders
Voce pode sobrescrever qualquer consulta com:
ORACLE_QUERY_INBOUNDORACLE_QUERY_STOCKORACLE_QUERY_PICKINGORACLE_QUERY_ORDERS_FLOW_BY_DATEORACLE_QUERY_BILLED_ORDERS_COUNT
Entrada por CLI
npm run chatParâ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-agentModo recomendado (EDGE_AGENT_MODE=reverse-client):
o cliente conecta no core em
EDGE_CORE_URLfaz poll em
POST /edge/pollresponde em
POST /edge/resultexecuta 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-clientEDGE_CORE_URL=http://host-do-core:3000EDGE_AGENT_TENANT_ID=cliente-demoEDGE_AGENT_TOKEN(ouEDGE_AGENT_SHARED_TOKEN)EDGE_AGENT_HTTP_TIMEOUT_MSEDGE_AGENT_RECONNECT_DELAY_MSEDGE_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 /healthePOST /agent/querypara core conectar direto no cliente.
Comandos já suportados
recebimentoestoqueseparacaominhas filiaistrocar filialajuda
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.
This server cannot be installed
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