- gti-scheduling-mcp: MCP server com ZProAdapter e KlingoAdapter (agendar() completo pros dois) - Workflow n8n 'Agendamento - GChat - Z-PRO': webhook -> AI Agent -> MCP -> Z-PRO, testado com sucesso real - Documentacao: specs, plano, runbook de problemas reais e export do workflow Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
21 KiB
Workflow n8n de Agendamento (GChat/Z-PRO → gti-scheduling-mcp) Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Construir, no n8n (https://devn8n.gtigrupo.com.br), o primeiro workflow que recebe mensagens de clientes via webhook do Z-PRO/GChat, conduz a conversa com um AI Agent para capturar os dados do agendamento, e chama a tool agendar do gti-scheduling-mcp (via ZProAdapter) para realizar o agendamento de fato.
Architecture: Webhook (Z-PRO) → node Code de normalização/guarda de step → AI Agent (OpenAI + memória de sessão por protocol + tool MCP agendar) → Respond to Webhook (texto puro). Construído incrementalmente: cada task adiciona uma camada e é validada isoladamente antes da próxima.
Tech Stack: n8n (self-hosted, devn8n.gtigrupo.com.br), node n8n-nodes-mcp (community, transporte stdio), gti-scheduling-mcp (já implementado, sem alterações de código nesta etapa), OpenAI Chat Model.
Global Constraints
- Nenhuma alteração de código/arquitetura no
gti-scheduling-mcpé necessária ou permitida nesta etapa — ele já expõe a toolagendarexatamente como o workflow precisa consumir. - O webhook precisa sempre responder de imediato, uma execução por mensagem — nunca segurar a resposta esperando mais mensagens (Z-PRO descarta mensagens se isso acontecer).
- A resposta ao Z-PRO é sempre texto puro, nunca JSON.
- Nenhuma credencial (token do Z-PRO, API key da OpenAI) é inserida por quem executa este plano de forma automática — todas vêm de Credentials do n8n, criadas manualmente pelo usuário na UI.
sessionIdda memória do agente =protocoldo payload do Z-PRO (nunca o telefone — o mesmo telefone pode ter vários atendimentos/protocolos).- Escopo: só
agendar()viaZProAdapter. Nada deconsultar_horarios, cancelamento, remarcação ou múltiplas clínicas nesta etapa (ver spec, seção "Fora de escopo").
Spec de referência: docs/superpowers/specs/2026-08-10-n8n-agendamento-workflow-design.md
Pré-requisitos (ação manual do usuário — fora do escopo de execução automática)
Estes itens não podem ser feitos pelas tasks abaixo (exigem acesso à infraestrutura do n8n/credenciais reais) e precisam estar prontos antes da Task 5:
- Instalar o node comunitário
n8n-nodes-mcpno n8n de vocês, seguindohttps://docs.n8n.io/integrations/community-nodes/installation/. - Definir a variável de ambiente
N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE=trueno processo do n8n (ex.: nodocker-compose.ymldo serviço n8n) e reiniciar o n8n — sem isso, o node comunitário não pode ser usado como tool dentro do AI Agent. - Criar uma Credential do tipo OpenAI no n8n (Settings → Credentials → OpenAI API), com uma API key válida.
- Criar uma Credential do tipo "MCP Client (Command Line) API" (fornecida pelo node
n8n-nodes-mcpapós instalado), com:- Command:
node - Arguments: caminho absoluto para
dist/index.jsdogti-scheduling-mcpno servidor onde o n8n roda (ex.:/opt/gti-scheduling-mcp/dist/index.js) — depende de onde o projeto for implantado, que é uma decisão de infraestrutura fora do escopo deste plano. - Environment Variables (uma por linha, formato
NOME=valor):SCHEDULING_PROVIDER=zpro ZPRO_BASE_URL=<url real da clínica> ZPRO_API_TOKEN=<token real da clínica> ZPRO_API_ID=<ApiID real da clínica — o GUID, não o número do registro>
- Command:
Sem os 4 itens acima, a Task 5 (conectar a tool MCP ao Agent) não pode ser validada de ponta a ponta — as Tasks 1 a 4 não dependem deles.
Task 1: Build do gti-scheduling-mcp e localização do dist/index.js
Files:
- Nenhum arquivo novo — apenas build do projeto existente.
Interfaces:
-
Produces: caminho absoluto de
dist/index.js, usado nas Tasks 5 e no pré-requisito 4 acima. -
Step 1: Rodar o build
cd "D:\PROJETOS\KLINGO-CLINICAL-SYSLIFE-GCHAT-AGENDAMENTO\gti-scheduling-mcp"
npm run build
Esperado: tsc -p tsconfig.json sem erros, e a pasta dist/ criada/atualizada.
- Step 2: Confirmar que
dist/index.jsexiste e roda
node "D:\PROJETOS\KLINGO-CLINICAL-SYSLIFE-GCHAT-AGENDAMENTO\gti-scheduling-mcp\dist\index.js" --help 2>&1 | head -5
Esperado: o processo inicia (mesmo que não reconheça --help, não deve dar erro de módulo não encontrado). Se o servidor esperar variáveis de ambiente obrigatórias e falhar por causa disso, isso confirma que o build está correto (o processo rodou, só não tinha config — comportamento esperado sem env vars).
Anote o caminho absoluto de dist/index.js — será necessário nos pré-requisitos e na Task 5.
Task 2: Esqueleto base do webhook (Webhook → Respond to Webhook)
Files: Nenhum arquivo local — cria o workflow no n8n via n8n_create_workflow.
Interfaces:
-
Produces:
workflowId(retornado pela chamada) — usado em todas as tasks seguintes. -
Step 1: Criar o workflow com dois nodes ligados diretamente
Chame a tool n8n_create_workflow com:
{
"name": "Agendamento - GChat - Z-PRO (clínica exemplo)",
"nodes": [
{
"id": "webhook-zpro",
"name": "Webhook Z-PRO",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2.1,
"position": [0, 0],
"parameters": {
"httpMethod": "POST",
"path": "agendamento-zpro-clinica-exemplo",
"responseMode": "responseNode"
}
},
{
"id": "respond-stub",
"name": "Respond to Webhook",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.5,
"position": [300, 0],
"parameters": {
"respondWith": "text",
"responseBody": "teste ok"
}
}
],
"connections": {
"Webhook Z-PRO": {
"main": [[{ "node": "Respond to Webhook", "type": "main", "index": 0 }]]
}
}
}
Guarde o id do workflow retornado — chamado de <workflowId> no restante deste plano.
- Step 2: Validar o workflow
Chame n8n_validate_workflow com {"id": "<workflowId>"}. Esperado: sem erros (pode haver avisos sobre o workflow estar inativo — normal, só ativamos na Task 6).
- Step 3: Ativar temporariamente e testar a resposta em texto puro
Ative o workflow (n8n_update_partial_workflow com operação {"type": "activateWorkflow"}), pegue a URL de produção do webhook (visível no node Webhook Z-PRO / na resposta da criação), e chame:
curl -i -X POST "https://devn8n.gtigrupo.com.br/webhook/agendamento-zpro-clinica-exemplo" \
-H "Content-Type: application/json" \
-d '{"step":"process","entrada":"oi","number":"5511999999999","contactName":"Teste","protocol":"protocolo-teste-1"}'
Esperado: HTTP 200, corpo exatamente teste ok (texto puro, sem aspas de JSON ao redor).
- Step 4: Desativar o workflow de novo
n8n_update_partial_workflow com {"type": "deactivateWorkflow"} — mantemos desativado até a Task 6, para não responder de verdade a mensagens reais enquanto ainda está incompleto.
Task 3: Node de normalização + guarda de step
Files: Nenhum arquivo local — edita o workflow <workflowId> via n8n_update_partial_workflow.
Interfaces:
-
Consumes:
<workflowId>da Task 2. -
Produces: campos
sessionId,mensagem,nomeContato,telefone,provider,clinicId,earlyReply— consumidos pela Task 4 (AI Agent) e peloIfdesta task. -
Step 1: Adicionar o node
Codede normalização
{
"type": "addNode",
"node": {
"id": "normalizar-entrada",
"name": "Normalizar entrada",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [150, -120],
"parameters": {
"mode": "runOnceForEachItem",
"language": "javaScript",
"jsCode": "const body = $input.item.json.body || {};\n\n// Configuração fixa desta cópia do workflow. Ajustar por clínica ao\n// duplicar este workflow para uma nova instância.\nconst clinicConfig = {\n provider: \"zpro\",\n clinicId: \"clinica-exemplo\",\n};\n\nif (body.step !== \"process\") {\n return {\n json: {\n earlyReply: true,\n sessionId: body.protocol || \"\",\n mensagem: \"\",\n nomeContato: \"\",\n telefone: \"\",\n provider: clinicConfig.provider,\n clinicId: clinicConfig.clinicId,\n },\n };\n}\n\nreturn {\n json: {\n earlyReply: false,\n sessionId: body.protocol || \"\",\n mensagem: String(body.entrada || \"\").trim(),\n nomeContato: body.contactName || \"\",\n telefone: body.number || \"\",\n provider: clinicConfig.provider,\n clinicId: clinicConfig.clinicId,\n },\n};"
}
}
}
- Step 2: Adicionar o node
Ifque decide entre resposta antecipada e fluxo normal
{
"type": "addNode",
"node": {
"id": "if-early-reply",
"name": "É step=process?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [400, -120],
"parameters": {
"conditions": {
"options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict" },
"combinator": "and",
"conditions": [
{
"id": "cond-early-reply",
"leftValue": "={{ $json.earlyReply }}",
"rightValue": true,
"operator": { "type": "boolean", "operation": "true", "singleValue": true }
}
]
}
}
}
}
- Step 3: Rewire — Webhook → Normalizar entrada → If
[
{ "type": "removeConnection", "source": "Webhook Z-PRO", "target": "Respond to Webhook" },
{ "type": "addConnection", "source": "Webhook Z-PRO", "target": "Normalizar entrada" },
{ "type": "addConnection", "source": "Normalizar entrada", "target": "É step=process?" }
]
- Step 4: Ligar a saída TRUE do If (índice 0) direto ao Respond to Webhook existente, com texto vazio
[
{ "type": "patchNodeField", "nodeName": "Respond to Webhook", "fieldPath": "parameters.responseBody", "patches": [{ "find": "teste ok", "replace": "={{ $json.mensagem || '' }}" }] },
{ "type": "addConnection", "source": "É step=process?", "sourceOutput": 0, "target": "Respond to Webhook" }
]
A saída FALSE (índice 1) do If fica sem conexão por enquanto — será ligada ao AI Agent na Task 4.
- Step 5: Validar
n8n_validate_workflow com {"id": "<workflowId>"}. Esperado: sem erros. Pode haver aviso de node com saída não conectada (É step=process? saída FALSE) — esperado nesta task, resolvido na Task 4.
- Step 6: Testar os dois caminhos
Ative o workflow, chame o webhook duas vezes:
curl -s -X POST "https://devn8n.gtigrupo.com.br/webhook/agendamento-zpro-clinica-exemplo" \
-H "Content-Type: application/json" \
-d '{"step":"outro","entrada":"x","number":"1","contactName":"y","protocol":"p1"}'
Esperado: corpo vazio (early reply, step diferente de process).
curl -s -X POST "https://devn8n.gtigrupo.com.br/webhook/agendamento-zpro-clinica-exemplo" \
-H "Content-Type: application/json" \
-d '{"step":"process","entrada":"Olá, quero marcar uma consulta","number":"5511999999999","contactName":"Fulano","protocol":"p2"}'
Esperado: corpo = Olá, quero marcar uma consulta (o Respond to Webhook está ecoando mensagem só para confirmar que os campos normalizados chegaram certos — será substituído pela resposta do Agent na Task 4).
Desative o workflow ao final (deactivateWorkflow), como na Task 2.
Task 4: AI Agent com memória de sessão (sem tool ainda)
Files: Nenhum arquivo local — edita <workflowId>.
Interfaces:
-
Consumes:
earlyReply(falso),sessionId,mensagem,nomeContato,telefoneda Task 3. -
Produces: texto de resposta do Agent, consumido pelo
Respond to Webhook. -
Step 1: Adicionar o node OpenAI Chat Model
{
"type": "addNode",
"node": {
"id": "openai-chat-model",
"name": "OpenAI Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"typeVersion": 1,
"position": [650, 100],
"parameters": {
"model": { "value": "gpt-4o-mini" },
"options": {}
},
"credentials": {
"openAiApi": { "id": "<ID da Credential OpenAI criada no pré-requisito 3>", "name": "OpenAI account" }
}
}
}
Substitua <ID da Credential OpenAI criada no pré-requisito 3> pelo ID real da credencial (visível na UI do n8n, em Settings → Credentials, ou via n8n_manage_credentials se disponível). Se preferir, este campo pode ser deixado para o usuário selecionar manualmente na UI do n8n após a criação do node — não bloqueia os steps seguintes.
- Step 2: Adicionar o node Simple Memory
{
"type": "addNode",
"node": {
"id": "memoria-sessao",
"name": "Memória da Sessão",
"type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
"typeVersion": 1.4,
"position": [650, 250],
"parameters": {
"sessionIdType": "customKey",
"sessionKey": "={{ $('Normalizar entrada').item.json.sessionId }}",
"contextWindowLength": 20
}
}
}
- Step 3: Adicionar o node AI Agent
{
"type": "addNode",
"node": {
"id": "agente-agendamento",
"name": "AI Agent",
"type": "@n8n/n8n-nodes-langchain.agent",
"typeVersion": 3.1,
"position": [650, -120],
"parameters": {
"promptType": "define",
"text": "={{ $json.mensagem }}",
"options": {
"systemMessage": "Você é o assistente de agendamento da clínica, conversando por WhatsApp. Regras:\n1. O nome de contato do WhatsApp é \"{{ $('Normalizar entrada').item.json.nomeContato }}\" e o telefone é \"{{ $('Normalizar entrada').item.json.telefone }}\" — mas SEMPRE confirme o nome completo do paciente com o cliente antes de agendar, mesmo que já tenha o nome de contato.\n2. Pergunte a data e o horário desejados para a consulta, e opcionalmente o motivo.\n3. Nunca invente informação que o cliente não deu.\n4. Responda sempre em português, em tom cordial e objetivo, adequado para WhatsApp (mensagens curtas).\n5. Quando tiver o nome completo confirmado e a data/hora desejada, você pode agendar de verdade.\n6. NUNCA agende mais de uma vez na mesma conversa — se o cliente já confirmou um agendamento nesta conversa, não agende de novo; informe que o agendamento já foi feito.\n7. Se o agendamento falhar, explique o problema ao cliente em linguagem simples e pergunte se ele quer tentar de novo com outros dados, ou avise que vai encaminhar para um atendente."
}
}
}
}
- Step 4: Conectar Chat Model e Memory ao Agent, e a saída FALSE do If ao Agent
[
{ "type": "addConnection", "source": "OpenAI Chat Model", "sourceOutput": "ai_languageModel", "target": "AI Agent", "targetInput": "ai_languageModel" },
{ "type": "addConnection", "source": "Memória da Sessão", "sourceOutput": "ai_memory", "target": "AI Agent", "targetInput": "ai_memory" },
{ "type": "addConnection", "source": "É step=process?", "sourceOutput": 1, "target": "AI Agent" },
{ "type": "addConnection", "source": "AI Agent", "target": "Respond to Webhook" }
]
- Step 5: Ajustar o Respond to Webhook para usar a saída do Agent
{
"type": "patchNodeField",
"nodeName": "Respond to Webhook",
"fieldPath": "parameters.responseBody",
"patches": [{ "find": "={{ $json.mensagem || '' }}", "replace": "={{ $json.output }}" }]
}
($json.output é o campo de texto padrão que o node AI Agent produz.)
- Step 6: Validar
n8n_validate_workflow com {"id": "<workflowId>"}. Esperado: sem erros. Se aparecer erro sobre a credencial OpenAI ausente/inválida, ajuste o credentials.openAiApi.id (Step 1) ou peça para o usuário selecionar a credencial manualmente na UI antes de prosseguir.
- Step 7: Testar a memória de sessão
Ative o workflow. Envie duas mensagens com o mesmo protocol:
curl -s -X POST "https://devn8n.gtigrupo.com.br/webhook/agendamento-zpro-clinica-exemplo" \
-H "Content-Type: application/json" \
-d '{"step":"process","entrada":"Meu nome completo é João da Silva Teste","number":"5511999999999","contactName":"João","protocol":"p-memoria-1"}'
curl -s -X POST "https://devn8n.gtigrupo.com.br/webhook/agendamento-zpro-clinica-exemplo" \
-H "Content-Type: application/json" \
-d '{"step":"process","entrada":"Qual é o meu nome completo que eu te falei?","number":"5511999999999","contactName":"João","protocol":"p-memoria-1"}'
Esperado: a segunda resposta menciona "João da Silva Teste" — confirma que a memória por sessionId=protocol está funcionando entre execuções separadas do webhook.
Desative o workflow ao final.
Task 5: Conectar a tool MCP agendar ao Agent
Pré-requisito: os 4 itens da seção "Pré-requisitos" no topo deste plano precisam estar prontos (node comunitário instalado, env var setada e n8n reiniciado, credenciais OpenAI e MCP criadas).
Files: Nenhum arquivo local — edita <workflowId>.
Interfaces:
-
Consumes: credencial MCP stdio criada no pré-requisito 4.
-
Produces: capacidade do Agent de chamar
agendarde verdade. -
Step 1: Adicionar o node MCP Client Tool
{
"type": "addNode",
"node": {
"id": "mcp-tool-agendar",
"name": "MCP - gti-scheduling-mcp",
"type": "n8n-nodes-mcp.mcpClientTool",
"typeVersion": 1,
"position": [900, 250],
"parameters": {
"connectionType": "cmd",
"include": "selected",
"includeTools": ["agendar"]
},
"credentials": {
"mcpClientCmdApi": { "id": "<ID da Credential MCP stdio criada no pré-requisito 4>", "name": "gti-scheduling-mcp (stdio)" }
}
}
}
Os nomes exatos do tipo de credencial (mcpClientCmdApi) e da propriedade connectionType podem variar conforme a versão instalada do pacote n8n-nodes-mcp — se a validação do Step 3 apontar um nome de campo diferente, ajuste conforme o que a própria UI do n8n mostrar ao adicionar o node manualmente (Settings do node → aba Credentials).
- Step 2: Conectar o node como tool do Agent
{
"type": "addConnection",
"source": "MCP - gti-scheduling-mcp",
"sourceOutput": "ai_tool",
"target": "AI Agent",
"targetInput": "ai_tool"
}
- Step 3: Validar
n8n_validate_workflow com {"id": "<workflowId>", "options": {"profile": "ai-friendly"}}. Corrija qualquer erro de nome de campo/credencial apontado (ver nota no Step 1).
- Step 4: Teste manual controlado, com dados fictícios, seguindo a mesma disciplina de segurança contra duplicidade do teste do
ZProAdapter
Ative o workflow. Envie, com um protocol novo e único:
curl -s -X POST "https://devn8n.gtigrupo.com.br/webhook/agendamento-zpro-clinica-exemplo" \
-H "Content-Type: application/json" \
-d '{"step":"process","entrada":"Oi, quero marcar uma consulta pra amanhã às 14h, meu nome completo é TESTE-N8N Paciente","number":"5511900000001","contactName":"Teste N8N","protocol":"p-e2e-1"}'
Continue a conversa (repetindo a chamada com o mesmo protocol, mudando só entrada) até o Agent confirmar o agendamento. Antes de cada nova tentativa, se algo parecer ter falhado, confira visualmente no Z-PRO se o agendamento já foi criado (mesma regra usada no teste manual do ZProAdapter) — não repita mensagens de confirmação "às cegas".
Confirme visualmente no Z-PRO que o agendamento fictício foi criado.
- Step 5: Desativar o workflow ao final do teste
n8n_update_partial_workflow com {"type": "deactivateWorkflow"} — só reativar definitivamente depois que o usuário revisar o resultado (Task 6).
Task 6: Revisão final e ativação
Files: Nenhum arquivo local.
- Step 1: Validação final completa
n8n_validate_workflow com {"id": "<workflowId>", "options": {"profile": "strict"}}. Resolver qualquer erro/aviso restante.
- Step 2: Apresentar o resumo ao usuário
Antes de ativar definitivamente, mostrar ao usuário: URL do webhook de produção, nome do workflow, e pedir confirmação de que o Z-PRO já está configurado para apontar o fluxo de agendamento desta clínica para essa URL.
- Step 3: Ativar
Só ativar (activateWorkflow) depois da confirmação explícita do usuário no Step 2 — ativar aqui significa que mensagens reais de clientes passam a ser processadas.
Observação sobre "commit"
Nem D:\PROJETOS\KLINGO-CLINICAL-SYSLIFE-GCHAT-AGENDAMENTO nem gti-scheduling-mcp são repositórios git — não há passo de git commit neste plano. O estado do workflow vive inteiramente no n8n; cada task é validada via n8n_validate_workflow e teste funcional (chamada real ao webhook), no lugar de testes automatizados de código.