19 KiB
MCP Server Nativo no n8n para o Z-PRO Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task (não subagent-driven-development — as tasks são chamadas de API do n8n, não commits de código; não há diff de git pra revisar por task). Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Reconstruir o "MCP Server" do Z-PRO inteiramente dentro do n8n (nodes oficiais MCP Server Trigger + Call n8n Sub-Workflow Tool), substituindo o processo Node.js externo conectado via node comunitário stdio.
Architecture: 3 workflows n8n — o servidor (MCP Server Trigger + 2 tools) e dois sub-workflows (agendar, consultar_horarios) que portam a lógica já validada de zpro.adapter.ts/zpro.mapper.ts/zpro.errors.ts para nodes Code/HTTP Request. O workflow de agendamento existente troca só o node MCP client (comunitário → oficial, HTTP).
Tech Stack: n8n (devn8n.gtigrupo.com.br), nodes oficiais @n8n/n8n-nodes-langchain (mcpTrigger, mcpClientTool, toolWorkflow), n8n-nodes-base (executeWorkflowTrigger, httpRequest, code, set).
Global Constraints
- Nenhum código TypeScript é alterado —
gti-scheduling-mcpfica intocado como referência. - Qualquer HTTP 2xx da API do Z-PRO é sucesso operacional, sem exceção — nunca reportar erro numa operação que o Z-PRO já aceitou (risco de agendamento duplicado). Ver
docs/zpro-api-mapping.mdesrc/adapters/zpro/zpro.adapter.tsdogti-scheduling-mcpcomo referência do comportamento exato a portar. consultar_horarioscontinua retornando erroNOT_SUPPORTEDpro Z-PRO — decisão de produto já tomada, sem chamada HTTP.- Nenhuma credencial é inserida por quem executa este plano de forma automática — a credencial Bearer do Z-PRO é criada manualmente pelo usuário na UI do n8n.
- Testes reais contra o Z-PRO seguem a disciplina já estabelecida: dado fictício, execução única por vez, conferência visual no Z-PRO antes de qualquer nova tentativa.
Spec de referência: docs/superpowers/specs/2026-08-10-mcp-server-nativo-n8n-zpro-design.md
Pré-requisito (ação manual do usuário)
Criar no n8n uma Credential do tipo Header Auth (Settings → Credentials → New → "Header Auth"):
- Name:
Authorization - Value:
Bearer <token real do Z-PRO>
Nomeie como ZPRO Bearer (nativo n8n). Anote o ID gerado — será necessário na Task 3.
Task 1: Sub-workflow "MCP Server - ZPRO - consultar_horarios"
Interfaces:
-
Produces: um workflow n8n chamado
MCP Server - ZPRO - consultar_horarios, com umExecute Workflow Trigger(modo passthrough) →Codeque sempre retorna{ success: false, error: { code: "NOT_SUPPORTED", message: "..." } }. -
Step 1: Criar o workflow
Chamar n8n_create_workflow:
{
"name": "MCP Server - ZPRO - consultar_horarios",
"nodes": [
{
"id": "trigger",
"name": "Execute Workflow Trigger",
"type": "n8n-nodes-base.executeWorkflowTrigger",
"typeVersion": 1.2,
"position": [0, 0],
"parameters": { "inputSource": "passthrough" }
},
{
"id": "not-supported",
"name": "Retornar NOT_SUPPORTED",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [260, 0],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "return {\n json: {\n success: false,\n error: {\n code: \"NOT_SUPPORTED\",\n message: \"O provider Z-PRO não oferece consulta de disponibilidade de horários.\",\n },\n },\n};"
}
}
],
"connections": {
"Execute Workflow Trigger": {
"main": [[{ "node": "Retornar NOT_SUPPORTED", "type": "main", "index": 0 }]]
}
}
}
Guarde o id retornado como <wf-consultar-horarios>.
- Step 2: Validar
n8n_validate_workflow com {"id": "<wf-consultar-horarios>"}. Esperado: sem erros.
- Step 3: Testar manualmente
Na UI do n8n, abrir o workflow e clicar em "Execute Workflow" (ou usar n8n_trigger_webhook_workflow/execução manual). Esperado: o node "Retornar NOT_SUPPORTED" produz { success: false, error: { code: "NOT_SUPPORTED", ... } }.
Task 2: Sub-workflow "MCP Server - ZPRO - agendar"
Interfaces:
-
Consumes: credencial
ZPRO Bearer (nativo n8n)do pré-requisito. -
Produces: um workflow n8n chamado
MCP Server - ZPRO - agendar, que recebeclinicId,startAt,endAt,patientName,patientPhone,patientId,title(todos string, sópatientName/startAt/endAt/clinicIdnormalmente preenchidos) e retorna{ success: true, data: {...} }ou{ success: false, error: {...} }. -
Step 1: Criar o workflow com todos os nodes e conexões
Chamar n8n_create_workflow:
{
"name": "MCP Server - ZPRO - agendar",
"nodes": [
{
"id": "trigger",
"name": "Execute Workflow Trigger",
"type": "n8n-nodes-base.executeWorkflowTrigger",
"typeVersion": 1.2,
"position": [0, 0],
"parameters": {
"inputSource": "workflowInputs",
"workflowInputs": {
"values": [
{ "name": "clinicId", "type": "string" },
{ "name": "startAt", "type": "string" },
{ "name": "endAt", "type": "string" },
{ "name": "patientName", "type": "string" },
{ "name": "patientPhone", "type": "string" },
{ "name": "patientId", "type": "string" },
{ "name": "title", "type": "string" }
]
}
}
},
{
"id": "config",
"name": "Config ZPRO",
"type": "n8n-nodes-base.set",
"typeVersion": 3.5,
"position": [260, 0],
"parameters": {
"mode": "manual",
"includeOtherFields": true,
"assignments": {
"assignments": [
{ "id": "cfg-baseurl", "name": "baseUrl", "type": "string", "value": "https://apigchat.gtigrupo.com.br" },
{ "id": "cfg-apiid", "name": "apiId", "type": "string", "value": "e372d4f0-1868-4855-8edf-5223f7839cc5" }
]
}
}
},
{
"id": "montar-corpo",
"name": "Montar corpo",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [520, 0],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const input = $json;\n\nconst title = (input.title && String(input.title).trim())\n ? String(input.title).trim()\n : `Consulta - ${input.patientName}`;\n\nconst requestBody = {\n title,\n contactName: input.patientName,\n startAt: input.startAt,\n endAt: input.endAt,\n status: \"pending\",\n};\nif (input.patientId) requestBody.contactId = input.patientId;\nif (input.patientPhone) requestBody.contactPhone = input.patientPhone;\n\nreturn {\n json: {\n baseUrl: input.baseUrl,\n apiId: input.apiId,\n requestBody,\n },\n};"
}
},
{
"id": "http-request",
"name": "POST appointment/create",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [780, 0],
"parameters": {
"method": "POST",
"url": "={{ $json.baseUrl }}/v2/api/external/{{ $json.apiId }}/appointment/create",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendBody": true,
"contentType": "json",
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify($json.requestBody) }}",
"options": {
"response": { "response": { "fullResponse": true, "neverError": true, "responseFormat": "autodetect" } },
"timeout": 15000
}
},
"credentials": {
"httpHeaderAuth": { "id": "SUBSTITUIR_PELO_ID_DA_CREDENCIAL", "name": "ZPRO Bearer (nativo n8n)" }
}
},
{
"id": "classificar",
"name": "Classificar resultado",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [1040, 0],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const status = $json.statusCode;\nconst rawBody = $json.body;\nconst bodyText = typeof rawBody === \"string\" ? rawBody : JSON.stringify(rawBody ?? \"\");\n\nfunction classifyError(status) {\n if (status === 401 || status === 403) {\n return { code: \"AUTHENTICATION_ERROR\", message: `Falha de autenticação na API do Z-PRO (status ${status}).` };\n }\n if (status === 404) {\n return { code: \"NOT_FOUND\", message: `Recurso não encontrado na API do Z-PRO (status ${status}). Verifique o ApiID.` };\n }\n if (status === 400 || status === 422) {\n return { code: \"VALIDATION_ERROR\", message: `A API do Z-PRO rejeitou os dados enviados (status ${status}).` };\n }\n return { code: \"ZPRO_API_ERROR\", message: `A API do Z-PRO retornou um erro inesperado (status ${status}).` };\n}\n\nif (status < 200 || status >= 300) {\n return { json: { success: false, error: classifyError(status) } };\n}\n\n// Qualquer 2xx é sucesso operacional. Tenta mapear o envelope real conhecido\n// (docs/zpro-api-mapping.md); nunca inventa campos que o corpo não confirma.\nlet parsed = null;\ntry {\n parsed = JSON.parse(bodyText);\n} catch (e) {\n parsed = null;\n}\n\nconst dataOk =\n parsed &&\n parsed.success === true &&\n parsed.data &&\n typeof parsed.data.id === \"number\" &&\n typeof parsed.data.startAt === \"string\" &&\n typeof parsed.data.endAt === \"string\" &&\n typeof parsed.data.status === \"string\";\n\nif (dataOk) {\n const statusMap = { pending: \"pending\", confirmed: \"confirmed\", cancelled: \"cancelled\" };\n return {\n json: {\n success: true,\n data: {\n id: String(parsed.data.id),\n startAt: parsed.data.startAt,\n endAt: parsed.data.endAt,\n status: statusMap[parsed.data.status] || \"pending\",\n responseMappingPending: false,\n },\n },\n };\n}\n\nconst input = $(\"Montar corpo\").item.json;\nreturn {\n json: {\n success: true,\n data: {\n startAt: input.requestBody.startAt,\n endAt: input.requestBody.endAt,\n status: \"pending\",\n responseMappingPending: true,\n },\n },\n};"
}
}
],
"connections": {
"Execute Workflow Trigger": {
"main": [[{ "node": "Config ZPRO", "type": "main", "index": 0 }]]
},
"Config ZPRO": {
"main": [[{ "node": "Montar corpo", "type": "main", "index": 0 }]]
},
"Montar corpo": {
"main": [[{ "node": "POST appointment/create", "type": "main", "index": 0 }]]
},
"POST appointment/create": {
"main": [[{ "node": "Classificar resultado", "type": "main", "index": 0 }]]
}
}
}
Antes de chamar a API, substitua "SUBSTITUIR_PELO_ID_DA_CREDENCIAL" pelo ID real da credencial criada no pré-requisito (buscar via n8n_manage_credentials action: "list", procurando "ZPRO Bearer (nativo n8n)").
Guarde o id do workflow retornado como <wf-agendar>.
- Step 2: Validar
n8n_validate_workflow com {"id": "<wf-agendar>"}. Esperado: sem erros. Se a credencial não tiver sido resolvida corretamente, o erro vai apontar isso — corrigir o ID antes de continuar.
- Step 3: Testar manualmente com dado fictício, execução única
Na UI do n8n, "Execute Workflow" com input de teste:
{
"clinicId": "clinica-exemplo",
"startAt": "2026-04-20T14:00:00.000Z",
"endAt": "2026-04-20T14:30:00.000Z",
"patientName": "TESTE-NATIVO-N8N Paciente",
"patientPhone": "5511900000000"
}
Esperado: { success: true, data: { id: "...", startAt: "...", endAt: "...", status: "pending", responseMappingPending: false } } (ou true pra responseMappingPending se o corpo real não bater com o envelope conhecido — não é erro, só não confirmado). Antes de rodar de novo por qualquer motivo, confira visualmente no Z-PRO se TESTE-NATIVO-N8N Paciente já apareceu.
Task 3: Workflow "MCP Server - ZPRO" (o servidor)
Interfaces:
-
Consumes:
<wf-agendar>(Task 2),<wf-consultar-horarios>(Task 1). -
Produces: endpoint MCP em
https://devn8n.gtigrupo.com.br/mcp/mcp-server-zpro-clinica-exemplo(ou path equivalente confirmado no Step 1). -
Step 1: Criar o workflow com o trigger e os dois nodes de tool
{
"name": "MCP Server - ZPRO",
"nodes": [
{
"id": "mcp-trigger",
"name": "MCP Server Trigger",
"type": "@n8n/n8n-nodes-langchain.mcpTrigger",
"typeVersion": 2,
"position": [0, 0],
"parameters": { "path": "mcp-server-zpro-clinica-exemplo", "authentication": "none" }
},
{
"id": "tool-agendar",
"name": "agendar",
"type": "@n8n/n8n-nodes-langchain.toolWorkflow",
"typeVersion": 2.2,
"position": [260, -100],
"parameters": {
"description": "Cria um agendamento real na clínica. Use quando tiver clinicId, data/hora de início e término (ISO 8601 com offset), e o nome completo do paciente confirmado.",
"source": "database",
"workflowId": { "value": "<wf-agendar>", "mode": "id" }
}
},
{
"id": "tool-consultar",
"name": "consultar_horarios",
"type": "@n8n/n8n-nodes-langchain.toolWorkflow",
"typeVersion": 2.2,
"position": [260, 100],
"parameters": {
"description": "Consulta horários disponíveis. Este provider (Z-PRO) não suporta essa consulta — sempre retorna erro NOT_SUPPORTED.",
"source": "database",
"workflowId": { "value": "<wf-consultar-horarios>", "mode": "id" }
}
}
],
"connections": {
"agendar": {
"ai_tool": [[{ "node": "MCP Server Trigger", "type": "ai_tool", "index": 0 }]]
},
"consultar_horarios": {
"ai_tool": [[{ "node": "MCP Server Trigger", "type": "ai_tool", "index": 0 }]]
}
}
}
Substitua <wf-agendar>/<wf-consultar-horarios> pelos IDs reais das Tasks 1 e 2.
Guarde o id deste workflow como <wf-mcp-server-zpro>.
- Step 2: Mapear os parâmetros da tool "agendar" na UI (passo manual — resourceMapper não é confiável via API)
Abrir o node agendar na UI do n8n. Em "Workflow Inputs", mapear cada campo que o sub-workflow espera (clinicId, startAt, endAt, patientName, patientPhone, patientId, title) para uma expressão $fromAI(...) com uma descrição clara pro modelo, no mesmo espírito do que já validamos no workflow de agendamento (ver docs/n8n-agendamento-runbook.md, seção 9 e 12 — usar offset -03:00, nunca Z/UTC, no startAt/endAt). Deixar clinicId como valor fixo (não $fromAI) se preferir travar por cópia do workflow, em vez de deixar o modelo decidir.
- Step 3: Validar e ativar
n8n_validate_workflow com {"id": "<wf-mcp-server-zpro>"}. Ativar (activateWorkflow). Anotar a URL completa do endpoint MCP mostrada no node MCP Server Trigger (algo como https://devn8n.gtigrupo.com.br/mcp/mcp-server-zpro-clinica-exemplo).
Task 4: Trocar o node MCP do workflow de agendamento (comunitário → oficial)
Interfaces:
-
Consumes: URL do endpoint MCP da Task 3.
-
Modifica: workflow
Agendamento - GChat - Z-PRO (clínica exemplo)(idyG2mi6ECBpPv4Hmn). -
Step 1: Desativar o workflow de agendamento antes de mexer
n8n_update_partial_workflow com {"id": "yG2mi6ECBpPv4Hmn", "operations": [{"type": "deactivateWorkflow"}]}.
- Step 2: Remover o node comunitário e adicionar o oficial
{
"id": "yG2mi6ECBpPv4Hmn",
"intent": "Trocar o node MCP comunitario (stdio) pelo oficial (HTTP), apontando para o MCP Server nativo do n8n",
"operations": [
{ "type": "removeNode", "nodeName": "MCP - gti-scheduling-mcp" },
{
"type": "addNode",
"node": {
"id": "mcp-tool-agendar-nativo",
"name": "MCP - ZPRO (nativo n8n)",
"type": "@n8n/n8n-nodes-langchain.mcpClientTool",
"typeVersion": 1.2,
"position": [1024, -128],
"parameters": {
"endpointUrl": "SUBSTITUIR_PELA_URL_DA_TASK_3",
"serverTransport": "httpStreamable",
"authentication": "none",
"include": "all"
}
}
},
{ "type": "addConnection", "source": "MCP - ZPRO (nativo n8n)", "target": "AI Agent", "sourceOutput": "ai_tool" }
]
}
Substitua "SUBSTITUIR_PELA_URL_DA_TASK_3" pela URL real anotada na Task 3, Step 3.
- Step 2b: Ajustar
toolParameters/prompt obsoletos
O systemMessage do AI Agent e qualquer referência a toolParameters/$fromAI feita especificamente pro node comunitário antigo não se aplicam mais do mesmo jeito — o mapeamento de parâmetros agora vive no node agendar do MCP Server - ZPRO (Task 3, Step 2). Revisar o systemMessage do AI Agent e confirmar que as regras de negócio (confirmar nome, não agendar duas vezes, fuso -03:00) continuam válidas — normalmente não precisa mudar texto nenhum, já que essas regras são sobre a conversa, não sobre o mecanismo da tool.
- Step 3: Validar
n8n_validate_workflow com {"id": "yG2mi6ECBpPv4Hmn", "options": {"profile": "ai-friendly"}}. Esperado: sem erros, e nenhum aviso sobre pacote comunitário (N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE) — sinal de que a migração pro node oficial funcionou.
Task 5: Teste de ponta a ponta e documentação
Interfaces:
-
Consumes: workflows das Tasks 1-4, todos ativos.
-
Step 1: Ativar o workflow de agendamento
n8n_update_partial_workflow com {"id": "yG2mi6ECBpPv4Hmn", "operations": [{"type": "activateWorkflow"}]}.
- Step 2: Teste real, dado fictício, conversa completa
Enviar ao webhook de produção (https://devn8n.gtigrupo.com.br/webhook/agendamento-zpro-clinica-exemplo) uma conversa completa com um protocol novo e único (nome completo, data/horário em linguagem natural, confirmação) — mesmo processo já validado antes. Uma execução por vez; conferir no Z-PRO antes de qualquer nova tentativa.
- Step 3: Desativar workflows de teste isolados (se ainda ativos) e manter só o necessário ativo
Confirmar que MCP Server - ZPRO está ativo (necessário em produção) e que os sub-workflows (Tasks 1-2) não precisam estar "ativos" — sub-workflows chamados via Call n8n Sub-Workflow Tool não são triggers próprios, não têm estado ativo/inativo relevante.
- Step 4: Atualizar o runbook
Adicionar uma seção em docs/n8n-agendamento-runbook.md documentando a migração: por que foi feita (bugs do node comunitário), a nova arquitetura (3 workflows), e que o gti-scheduling-mcp/node comunitário ficam como fallback caso o MCP nativo apresente alguma limitação não prevista.
- Step 5: Commit
cd "D:\PROJETOS\KLINGO-CLINICAL-SYSLIFE-GCHAT-AGENDAMENTO"
git add docs/n8n-agendamento-runbook.md
git commit -m "docs: registra migracao do MCP Server ZPRO para nodes nativos do n8n
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>"
(Os workflows em si vivem no n8n, não em arquivo — não há mais nada pra commitar além da atualização de documentação. Se quiser manter um export atualizado como já fizemos antes, repetir o processo de n8n_get_workflow + salvar em docs/n8n-workflows/ pros 3 novos workflows, opcional.)
Observação sobre execução
Assim como no plano anterior (workflow de agendamento), estas tasks são chamadas de API ao n8n, não commits de código — a skill executing-plans deve tratar cada Step como uma chamada de ferramenta + validação, não um ciclo de teste automatizado. Onde a documentação do n8n exige um passo manual na UI (Task 3, Step 2 — mapeamento de resourceMapper), isso fica explícito no step, não é algo pra tentar automatizar via API.