Files
KLINGO-CLINICAL-SYSLIFE-GCHAT/docs/superpowers/plans/2026-08-10-n8n-agendamento-workflow.md
T
GustavoandClaude Sonnet 5 257afe98c0 Snapshot: gti-scheduling-mcp (Z-PRO + Klingo adapters) e workflow n8n de agendamento funcionando de ponta a ponta
- 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>
2026-08-10 17:23:10 -03:00

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 tool agendar exatamente 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.
  • sessionId da memória do agente = protocol do payload do Z-PRO (nunca o telefone — o mesmo telefone pode ter vários atendimentos/protocolos).
  • Escopo: só agendar() via ZProAdapter. Nada de consultar_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:

  1. Instalar o node comunitário n8n-nodes-mcp no n8n de vocês, seguindo https://docs.n8n.io/integrations/community-nodes/installation/.
  2. Definir a variável de ambiente N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE=true no processo do n8n (ex.: no docker-compose.yml do serviço n8n) e reiniciar o n8n — sem isso, o node comunitário não pode ser usado como tool dentro do AI Agent.
  3. Criar uma Credential do tipo OpenAI no n8n (Settings → Credentials → OpenAI API), com uma API key válida.
  4. Criar uma Credential do tipo "MCP Client (Command Line) API" (fornecida pelo node n8n-nodes-mcp após instalado), com:
    • Command: node
    • Arguments: caminho absoluto para dist/index.js do gti-scheduling-mcp no 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>
      

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.js existe 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 pelo If desta task.

  • Step 1: Adicionar o node Code de 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 If que 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, telefone da 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 agendar de 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.