Plano: MCP Server nativo no n8n para o Z-PRO

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Gustavo
2026-08-10 18:03:30 -03:00
co-authored by Claude Sonnet 5
parent 3f10ab5efd
commit 8641d68f4f
@@ -0,0 +1,384 @@
# 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`:
```json
{
"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`:
```json
{
"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:
```json
{
"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**
```json
{
"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**
```json
{
"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**
```bash
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.