diff --git a/docs/superpowers/plans/2026-08-10-mcp-server-nativo-n8n-zpro.md b/docs/superpowers/plans/2026-08-10-mcp-server-nativo-n8n-zpro.md new file mode 100644 index 0000000..13a5b7d --- /dev/null +++ b/docs/superpowers/plans/2026-08-10-mcp-server-nativo-n8n-zpro.md @@ -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 ` + +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.