# 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 ` 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 ``. - [ ] **Step 2: Validar** `n8n_validate_workflow` com `{"id": ""}`. 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 ``. - [ ] **Step 2: Validar** `n8n_validate_workflow` com `{"id": ""}`. 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: `` (Task 2), `` (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": "", "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": "", "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 ``/`` pelos IDs reais das Tasks 1 e 2. Guarde o `id` deste workflow como ``. - [ ] **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": ""}`. 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 " ``` (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.