- 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>
189 lines
8.3 KiB
JSON
189 lines
8.3 KiB
JSON
{
|
|
"name": "Agendamento - GChat - Z-PRO (clínica exemplo)",
|
|
"nodes": [
|
|
{
|
|
"parameters": {
|
|
"httpMethod": "POST",
|
|
"path": "agendamento-zpro-clinica-exemplo",
|
|
"responseMode": "responseNode",
|
|
"options": {}
|
|
},
|
|
"id": "webhook-zpro",
|
|
"name": "Webhook Z-PRO",
|
|
"type": "n8n-nodes-base.webhook",
|
|
"typeVersion": 2.1,
|
|
"position": [-208, -176]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"respondWith": "text",
|
|
"responseBody": "={{ $json.output }}",
|
|
"options": {}
|
|
},
|
|
"id": "respond-stub",
|
|
"name": "Respond to Webhook",
|
|
"type": "n8n-nodes-base.respondToWebhook",
|
|
"typeVersion": 1.5,
|
|
"position": [224, 48]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"mode": "runOnceForEachItem",
|
|
"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};"
|
|
},
|
|
"id": "normalizar-entrada",
|
|
"name": "Normalizar entrada",
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [-16, -176],
|
|
"onError": "continueErrorOutput"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"conditions": {
|
|
"options": {
|
|
"caseSensitive": true,
|
|
"leftValue": "",
|
|
"typeValidation": "strict",
|
|
"version": 2
|
|
},
|
|
"combinator": "and",
|
|
"conditions": [
|
|
{
|
|
"id": "cond-early-reply",
|
|
"leftValue": "={{ $json.earlyReply }}",
|
|
"rightValue": true,
|
|
"operator": {
|
|
"type": "boolean",
|
|
"operation": "true",
|
|
"singleValue": true
|
|
}
|
|
}
|
|
]
|
|
},
|
|
"options": {}
|
|
},
|
|
"id": "if-early-reply",
|
|
"name": "É step=process?",
|
|
"type": "n8n-nodes-base.if",
|
|
"typeVersion": 2.3,
|
|
"position": [240, -336]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"model": { "mode": "id", "value": "gpt-4o-mini" },
|
|
"builtInTools": {},
|
|
"options": {}
|
|
},
|
|
"id": "openai-chat-model",
|
|
"name": "OpenAI Chat Model",
|
|
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
|
|
"typeVersion": 1.3,
|
|
"position": [512, -64],
|
|
"credentials": {
|
|
"openAiApi": { "id": "PRECISA_RECRIAR_NO_SERVIDOR_NOVO", "name": "GPT" }
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"sessionIdType": "customKey",
|
|
"sessionKey": "={{ $json.sessionId }}",
|
|
"contextWindowLength": 20
|
|
},
|
|
"id": "memoria-sessao",
|
|
"name": "Memória da Sessão",
|
|
"type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
|
|
"typeVersion": 1.4,
|
|
"position": [672, -96]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"promptType": "define",
|
|
"text": "={{ $json.mensagem }}",
|
|
"options": {
|
|
"systemMessage": "=Você é o assistente de agendamento da clínica, conversando por WhatsApp. A data de hoje é {{ $now.setZone('America/Sao_Paulo').toFormat('yyyy-MM-dd (cccc)') }}, fuso America/Sao_Paulo — use isso pra calcular qualquer data relativa (\"amanhã\", \"sexta que vem\", etc.), nunca assuma outro ano. clinicId e telefone do paciente já são preenchidos automaticamente pelo sistema — você só precisa decidir data/hora e confirmar o nome completo.\n\nRegras:\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. O horário que o cliente disser (ex.: \"10h\") é sempre horário local de Brasília (America/Sao_Paulo, UTC-3) — use esse mesmo número de hora ao montar startAt/endAt, com offset -03:00 (nunca converta pra UTC/Z, nunca some ou subtraia horas).\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."
|
|
}
|
|
},
|
|
"id": "agente-agendamento",
|
|
"name": "AI Agent",
|
|
"type": "@n8n/n8n-nodes-langchain.agent",
|
|
"typeVersion": 3.1,
|
|
"position": [560, -320],
|
|
"onError": "continueErrorOutput"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"operation": "executeTool",
|
|
"toolName": "agendar",
|
|
"toolParameters": "={{ JSON.stringify({ clinicId: $('Normalizar entrada').item.json.clinicId, startAt: $fromAI('startAt', 'Data e hora de inicio da consulta, no horario local pedido pelo cliente, em ISO 8601 com offset -03:00 (America/Sao_Paulo) -- NUNCA use Z/UTC. Exemplo: cliente pediu 10h -> 2026-08-14T10:00:00-03:00', 'string'), endAt: $fromAI('endAt', 'Data e hora de termino da consulta, mesmo formato do startAt (offset -03:00, nunca Z/UTC), geralmente 30 minutos apos o startAt', 'string'), patient: { name: $fromAI('patientName', 'Nome completo do paciente, ja confirmado com o cliente na conversa', 'string'), phone: $('Normalizar entrada').item.json.telefone } }) }}"
|
|
},
|
|
"id": "mcp-tool-agendar",
|
|
"name": "MCP - gti-scheduling-mcp",
|
|
"type": "n8n-nodes-mcp.mcpClientTool",
|
|
"typeVersion": 1,
|
|
"position": [1024, -128],
|
|
"credentials": {
|
|
"mcpClientApi": { "id": "PRECISA_RECRIAR_NO_SERVIDOR_NOVO", "name": "Client (STDIO) account" }
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"assignments": {
|
|
"assignments": [
|
|
{
|
|
"id": "erro-output",
|
|
"name": "output",
|
|
"type": "string",
|
|
"value": "Desculpe, tive um problema técnico agora e não consegui continuar. Pode tentar de novo em alguns minutos, ou aguardar que alguém da nossa equipe vai te atender."
|
|
}
|
|
]
|
|
},
|
|
"options": {}
|
|
},
|
|
"id": "erro-resposta-padrao",
|
|
"name": "Erro - Resposta padrão",
|
|
"type": "n8n-nodes-base.set",
|
|
"typeVersion": 3.5,
|
|
"position": [240, -176]
|
|
}
|
|
],
|
|
"connections": {
|
|
"Webhook Z-PRO": {
|
|
"main": [[{ "node": "Normalizar entrada", "type": "main", "index": 0 }]]
|
|
},
|
|
"Normalizar entrada": {
|
|
"main": [
|
|
[{ "node": "É step=process?", "type": "main", "index": 0 }],
|
|
[{ "node": "Erro - Resposta padrão", "type": "main", "index": 0 }]
|
|
]
|
|
},
|
|
"OpenAI Chat Model": {
|
|
"ai_languageModel": [[{ "node": "AI Agent", "type": "ai_languageModel", "index": 0 }]]
|
|
},
|
|
"Memória da Sessão": {
|
|
"ai_memory": [[{ "node": "AI Agent", "type": "ai_memory", "index": 0 }]]
|
|
},
|
|
"É step=process?": {
|
|
"main": [
|
|
[],
|
|
[{ "node": "AI Agent", "type": "main", "index": 0 }]
|
|
]
|
|
},
|
|
"AI Agent": {
|
|
"main": [
|
|
[{ "node": "Respond to Webhook", "type": "main", "index": 0 }],
|
|
[{ "node": "Erro - Resposta padrão", "type": "main", "index": 0 }]
|
|
]
|
|
},
|
|
"MCP - gti-scheduling-mcp": {
|
|
"ai_tool": [[{ "node": "AI Agent", "type": "ai_tool", "index": 0 }]]
|
|
},
|
|
"Erro - Resposta padrão": {
|
|
"main": [[{ "node": "Respond to Webhook", "type": "main", "index": 0 }]]
|
|
}
|
|
},
|
|
"settings": {
|
|
"executionOrder": "v1"
|
|
}
|
|
}
|