Files
KLINGO-CLINICAL-SYSLIFE-GCHAT/docs/superpowers/plans/2026-08-10-mcp-server-nativo-n8n-zpro.md
2026-08-10 18:03:30 -03:00

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-mcp fica 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.md e src/adapters/zpro/zpro.adapter.ts do gti-scheduling-mcp como referência do comportamento exato a portar.
  • consultar_horarios continua retornando erro NOT_SUPPORTED pro 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 um Execute Workflow Trigger (modo passthrough) → Code que 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 recebe clinicId, startAt, endAt, patientName, patientPhone, patientId, title (todos string, só patientName/startAt/endAt/clinicId normalmente 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) (id yG2mi6ECBpPv4Hmn).

  • 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.