Files
KLINGO-CLINICAL-SYSLIFE-GCHAT/docs/superpowers/plans/2026-08-11-mcp-server-nativo-n8n-clinicall.md
T
GustavoandClaude Sonnet 5 fa806c5ead Plano: MCP Server nativo no n8n para o ClinicAll
10 tasks: 5 sub-workflows de catálogo (unidade, especialidade,
convenio, profissional, procedimento) com esqueleto quase idêntico,
sub-workflow paciente, sub-workflow consultar_horarios (GET
/disponibilidade com route params), sub-workflow agendar (PUT
/agendamento com a mesma proteção 2xx=sucesso), servidor MCP com as
8 tools, e revisão final + commit. Nenhuma credencial criada
(mecanismo de auth desconhecido, authentication:none) -- sem
clínica ClinicAll real disponível, validação só estrutural.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11 08:51:36 -03:00

48 KiB

MCP Server Nativo no n8n para o ClinicAll 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: Construir o "MCP Server" do ClinicAll inteiramente dentro do n8n (nodes oficiais MCP Server Trigger + Call n8n Sub-Workflow Tool), quarta aplicação do padrão já validado no Z-PRO e pronto estruturalmente no Klingo e no SysLife, cobrindo as 8 tools necessárias para um agendamento completo.

Architecture: 9 workflows n8n — o servidor (MCP Server Trigger + 8 tools) e oito sub-workflows portando os endpoints do docs/API Clinicall.pdf (Focus Tecnologia em Sistemas) para nodes Code/HTTP Request. Cinco tools de catálogo (unidade, especialidade, convenio, profissional, procedimento) seguem um esqueleto quase idêntico; paciente é parecida; consultar_horarios usa route params; agendar escreve de verdade (PUT) com a mesma proteção contra duplicidade (2xx sempre é sucesso) já usada nos outros três providers.

Tech Stack: n8n (devn8n.gtigrupo.com.br), nodes oficiais @n8n/n8n-nodes-langchain (mcpTrigger, toolWorkflow), n8n-nodes-base (executeWorkflowTrigger, httpRequest, code, set).

Global Constraints

  • Nenhum código TypeScript é alterado (não há adapter TypeScript pro ClinicAll — o manual PDF é a única fonte de verdade).
  • Qualquer HTTP 2xx de PUT /agendamento é sucesso operacional, sem exceção — nunca reportar erro numa operação que o ClinicAll já aceitou (risco de agendamento duplicado/conflito).
  • As 7 tools de leitura nunca reformatam o corpo de resposta em caso de sucesso — devolvem o corpo cru como veio.
  • Classificação de erro (todas as 8 tools) é só por status HTTP, taxonomia genérica idêntica à já usada nos outros três providers — o manual não documenta nenhum código de erro específico do ClinicAll para estes controllers.
  • Nenhuma credencial é criada — o mecanismo de autenticação real do ClinicAll é desconhecido (ver spec). Todos os nodes HTTP usam authentication: none. Isso é intencional, não um passo pulado.
  • /agendamento usa PUT — suposição não verificada (o manual lista GET/POST/PUT/DELETE sem mapear qual verbo concilia um idAgenda existente). Documentado na spec; se ao testar contra uma clínica real PUT não funcionar, tentar POST antes de qualquer outra hipótese.
  • As chaves de query string opcionais dos endpoints de catálogo (nome) são uma inferência razoável a partir do padrão de nomenclatura do manual (não há exemplo de URL com esse parâmetro no documento) — só grp (em procedimentos) tem exemplo literal (?grp=consulta). Documentado como suposição, não fato confirmado.
  • Sem clínica ClinicAll real disponível: nenhuma chamada HTTP real é feita. Validação é só estrutural (n8n_validate_workflow). Os 9 workflows ficam inativos ao final.

Spec de referência: docs/superpowers/specs/2026-08-11-mcp-server-nativo-n8n-clinicall-design.md


Task 1: Sub-workflow "MCP Server - ClinicAll - unidade"

Interfaces:

  • Produces: workflow MCP Server - ClinicAll - unidade, recebe nome (opcional), retorna o corpo cru de GET /datasnap/rest/TEntityController/unidades ou erro classificado.

  • Step 1: Criar o workflow

{
  "name": "MCP Server - ClinicAll - unidade",
  "nodes": [
    {
      "id": "trigger",
      "name": "Execute Workflow Trigger",
      "type": "n8n-nodes-base.executeWorkflowTrigger",
      "typeVersion": 1.2,
      "position": [0, 0],
      "parameters": {
        "inputSource": "workflowInputs",
        "workflowInputs": { "values": [{ "name": "nome", "type": "string" }] }
      }
    },
    {
      "id": "config",
      "name": "Config ClinicAll",
      "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": "http://SUBSTITUIR-PELO-HOST-REAL-DA-CLINICA:PORTA" }
          ]
        }
      }
    },
    {
      "id": "montar-query",
      "name": "Montar query",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [520, 0],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const input = $json;\nconst queryParams = {};\nif (input.nome) queryParams.nome = input.nome;\n\nreturn {\n  json: {\n    baseUrl: input.baseUrl,\n    queryParams,\n  },\n};"
      }
    },
    {
      "id": "get-unidades",
      "name": "GET /unidades",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [780, 0],
      "parameters": {
        "method": "GET",
        "url": "={{ $json.baseUrl }}/datasnap/rest/TEntityController/unidades",
        "authentication": "none",
        "sendQuery": true,
        "specifyQuery": "json",
        "jsonQuery": "={{ JSON.stringify($json.queryParams) }}",
        "options": {
          "response": { "response": { "fullResponse": true, "neverError": true, "responseFormat": "autodetect" } },
          "timeout": 15000
        }
      }
    },
    {
      "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;\n\nfunction classifyError(status, context) {\n  if (status === 401 || status === 403) {\n    return { code: \"AUTHENTICATION_ERROR\", message: `Falha de autenticação na API do ClinicAll ao ${context} (status ${status}).` };\n  }\n  if (status === 404) {\n    return { code: \"NOT_FOUND\", message: `Recurso não encontrado na API do ClinicAll ao ${context} (status ${status}).` };\n  }\n  if (status === 400 || status === 422) {\n    return { code: \"VALIDATION_ERROR\", message: `A API do ClinicAll rejeitou os dados enviados ao ${context} (status ${status}).` };\n  }\n  return { code: \"CLINICALL_API_ERROR\", message: `A API do ClinicAll retornou um erro inesperado ao ${context} (status ${status}).` };\n}\n\nif (status < 200 || status >= 300) {\n  return { json: { success: false, error: classifyError(status, \"consultar unidades\") } };\n}\n\nreturn { json: { success: true, data: rawBody } };"
      }
    }
  ],
  "connections": {
    "Execute Workflow Trigger": { "main": [[{ "node": "Config ClinicAll", "type": "main", "index": 0 }]] },
    "Config ClinicAll": { "main": [[{ "node": "Montar query", "type": "main", "index": 0 }]] },
    "Montar query": { "main": [[{ "node": "GET /unidades", "type": "main", "index": 0 }]] },
    "GET /unidades": { "main": [[{ "node": "Classificar resultado", "type": "main", "index": 0 }]] }
  }
}

Guarde o id retornado como <wf-unidade>.

  • Step 2: Validar

n8n_validate_workflow com {"id": "<wf-unidade>"}. Esperado: sem erros.


Task 2: Sub-workflow "MCP Server - ClinicAll - especialidade"

Interfaces:

  • Produces: workflow MCP Server - ClinicAll - especialidade, recebe nome (opcional), retorna o corpo cru de GET /datasnap/rest/TEntityController/especialidades ou erro classificado.

  • Step 1: Criar o workflow

Mesmo esqueleto exato da Task 1, trocando endpoint e mensagem de contexto:

{
  "name": "MCP Server - ClinicAll - especialidade",
  "nodes": [
    {
      "id": "trigger",
      "name": "Execute Workflow Trigger",
      "type": "n8n-nodes-base.executeWorkflowTrigger",
      "typeVersion": 1.2,
      "position": [0, 0],
      "parameters": {
        "inputSource": "workflowInputs",
        "workflowInputs": { "values": [{ "name": "nome", "type": "string" }] }
      }
    },
    {
      "id": "config",
      "name": "Config ClinicAll",
      "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": "http://SUBSTITUIR-PELO-HOST-REAL-DA-CLINICA:PORTA" }
          ]
        }
      }
    },
    {
      "id": "montar-query",
      "name": "Montar query",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [520, 0],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const input = $json;\nconst queryParams = {};\nif (input.nome) queryParams.nome = input.nome;\n\nreturn {\n  json: {\n    baseUrl: input.baseUrl,\n    queryParams,\n  },\n};"
      }
    },
    {
      "id": "get-especialidades",
      "name": "GET /especialidades",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [780, 0],
      "parameters": {
        "method": "GET",
        "url": "={{ $json.baseUrl }}/datasnap/rest/TEntityController/especialidades",
        "authentication": "none",
        "sendQuery": true,
        "specifyQuery": "json",
        "jsonQuery": "={{ JSON.stringify($json.queryParams) }}",
        "options": {
          "response": { "response": { "fullResponse": true, "neverError": true, "responseFormat": "autodetect" } },
          "timeout": 15000
        }
      }
    },
    {
      "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;\n\nfunction classifyError(status, context) {\n  if (status === 401 || status === 403) {\n    return { code: \"AUTHENTICATION_ERROR\", message: `Falha de autenticação na API do ClinicAll ao ${context} (status ${status}).` };\n  }\n  if (status === 404) {\n    return { code: \"NOT_FOUND\", message: `Recurso não encontrado na API do ClinicAll ao ${context} (status ${status}).` };\n  }\n  if (status === 400 || status === 422) {\n    return { code: \"VALIDATION_ERROR\", message: `A API do ClinicAll rejeitou os dados enviados ao ${context} (status ${status}).` };\n  }\n  return { code: \"CLINICALL_API_ERROR\", message: `A API do ClinicAll retornou um erro inesperado ao ${context} (status ${status}).` };\n}\n\nif (status < 200 || status >= 300) {\n  return { json: { success: false, error: classifyError(status, \"consultar especialidades\") } };\n}\n\nreturn { json: { success: true, data: rawBody } };"
      }
    }
  ],
  "connections": {
    "Execute Workflow Trigger": { "main": [[{ "node": "Config ClinicAll", "type": "main", "index": 0 }]] },
    "Config ClinicAll": { "main": [[{ "node": "Montar query", "type": "main", "index": 0 }]] },
    "Montar query": { "main": [[{ "node": "GET /especialidades", "type": "main", "index": 0 }]] },
    "GET /especialidades": { "main": [[{ "node": "Classificar resultado", "type": "main", "index": 0 }]] }
  }
}

Guarde o id retornado como <wf-especialidade>.

  • Step 2: Validar

n8n_validate_workflow com {"id": "<wf-especialidade>"}. Esperado: sem erros.


Task 3: Sub-workflow "MCP Server - ClinicAll - convenio"

Interfaces:

  • Produces: workflow MCP Server - ClinicAll - convenio, recebe nome (opcional), retorna o corpo cru de GET /datasnap/rest/TEntityController/convenios ou erro classificado.

  • Step 1: Criar o workflow

Mesmo esqueleto exato da Task 1/2:

{
  "name": "MCP Server - ClinicAll - convenio",
  "nodes": [
    {
      "id": "trigger",
      "name": "Execute Workflow Trigger",
      "type": "n8n-nodes-base.executeWorkflowTrigger",
      "typeVersion": 1.2,
      "position": [0, 0],
      "parameters": {
        "inputSource": "workflowInputs",
        "workflowInputs": { "values": [{ "name": "nome", "type": "string" }] }
      }
    },
    {
      "id": "config",
      "name": "Config ClinicAll",
      "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": "http://SUBSTITUIR-PELO-HOST-REAL-DA-CLINICA:PORTA" }
          ]
        }
      }
    },
    {
      "id": "montar-query",
      "name": "Montar query",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [520, 0],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const input = $json;\nconst queryParams = {};\nif (input.nome) queryParams.nome = input.nome;\n\nreturn {\n  json: {\n    baseUrl: input.baseUrl,\n    queryParams,\n  },\n};"
      }
    },
    {
      "id": "get-convenios",
      "name": "GET /convenios",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [780, 0],
      "parameters": {
        "method": "GET",
        "url": "={{ $json.baseUrl }}/datasnap/rest/TEntityController/convenios",
        "authentication": "none",
        "sendQuery": true,
        "specifyQuery": "json",
        "jsonQuery": "={{ JSON.stringify($json.queryParams) }}",
        "options": {
          "response": { "response": { "fullResponse": true, "neverError": true, "responseFormat": "autodetect" } },
          "timeout": 15000
        }
      }
    },
    {
      "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;\n\nfunction classifyError(status, context) {\n  if (status === 401 || status === 403) {\n    return { code: \"AUTHENTICATION_ERROR\", message: `Falha de autenticação na API do ClinicAll ao ${context} (status ${status}).` };\n  }\n  if (status === 404) {\n    return { code: \"NOT_FOUND\", message: `Recurso não encontrado na API do ClinicAll ao ${context} (status ${status}).` };\n  }\n  if (status === 400 || status === 422) {\n    return { code: \"VALIDATION_ERROR\", message: `A API do ClinicAll rejeitou os dados enviados ao ${context} (status ${status}).` };\n  }\n  return { code: \"CLINICALL_API_ERROR\", message: `A API do ClinicAll retornou um erro inesperado ao ${context} (status ${status}).` };\n}\n\nif (status < 200 || status >= 300) {\n  return { json: { success: false, error: classifyError(status, \"consultar convênios\") } };\n}\n\nreturn { json: { success: true, data: rawBody } };"
      }
    }
  ],
  "connections": {
    "Execute Workflow Trigger": { "main": [[{ "node": "Config ClinicAll", "type": "main", "index": 0 }]] },
    "Config ClinicAll": { "main": [[{ "node": "Montar query", "type": "main", "index": 0 }]] },
    "Montar query": { "main": [[{ "node": "GET /convenios", "type": "main", "index": 0 }]] },
    "GET /convenios": { "main": [[{ "node": "Classificar resultado", "type": "main", "index": 0 }]] }
  }
}

Guarde o id retornado como <wf-convenio>.

  • Step 2: Validar

n8n_validate_workflow com {"id": "<wf-convenio>"}. Esperado: sem erros.


Task 4: Sub-workflow "MCP Server - ClinicAll - profissional"

Interfaces:

  • Produces: workflow MCP Server - ClinicAll - profissional, recebe nome (opcional), retorna o corpo cru de GET /datasnap/rest/TEntityController/profissionais (já inclui especialidades[]/horarios[] de cada profissional) ou erro classificado.

  • Step 1: Criar o workflow

Mesmo esqueleto exato da Task 1/2/3:

{
  "name": "MCP Server - ClinicAll - profissional",
  "nodes": [
    {
      "id": "trigger",
      "name": "Execute Workflow Trigger",
      "type": "n8n-nodes-base.executeWorkflowTrigger",
      "typeVersion": 1.2,
      "position": [0, 0],
      "parameters": {
        "inputSource": "workflowInputs",
        "workflowInputs": { "values": [{ "name": "nome", "type": "string" }] }
      }
    },
    {
      "id": "config",
      "name": "Config ClinicAll",
      "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": "http://SUBSTITUIR-PELO-HOST-REAL-DA-CLINICA:PORTA" }
          ]
        }
      }
    },
    {
      "id": "montar-query",
      "name": "Montar query",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [520, 0],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const input = $json;\nconst queryParams = {};\nif (input.nome) queryParams.nome = input.nome;\n\nreturn {\n  json: {\n    baseUrl: input.baseUrl,\n    queryParams,\n  },\n};"
      }
    },
    {
      "id": "get-profissionais",
      "name": "GET /profissionais",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [780, 0],
      "parameters": {
        "method": "GET",
        "url": "={{ $json.baseUrl }}/datasnap/rest/TEntityController/profissionais",
        "authentication": "none",
        "sendQuery": true,
        "specifyQuery": "json",
        "jsonQuery": "={{ JSON.stringify($json.queryParams) }}",
        "options": {
          "response": { "response": { "fullResponse": true, "neverError": true, "responseFormat": "autodetect" } },
          "timeout": 15000
        }
      }
    },
    {
      "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;\n\nfunction classifyError(status, context) {\n  if (status === 401 || status === 403) {\n    return { code: \"AUTHENTICATION_ERROR\", message: `Falha de autenticação na API do ClinicAll ao ${context} (status ${status}).` };\n  }\n  if (status === 404) {\n    return { code: \"NOT_FOUND\", message: `Recurso não encontrado na API do ClinicAll ao ${context} (status ${status}).` };\n  }\n  if (status === 400 || status === 422) {\n    return { code: \"VALIDATION_ERROR\", message: `A API do ClinicAll rejeitou os dados enviados ao ${context} (status ${status}).` };\n  }\n  return { code: \"CLINICALL_API_ERROR\", message: `A API do ClinicAll retornou um erro inesperado ao ${context} (status ${status}).` };\n}\n\nif (status < 200 || status >= 300) {\n  return { json: { success: false, error: classifyError(status, \"consultar profissionais\") } };\n}\n\nreturn { json: { success: true, data: rawBody } };"
      }
    }
  ],
  "connections": {
    "Execute Workflow Trigger": { "main": [[{ "node": "Config ClinicAll", "type": "main", "index": 0 }]] },
    "Config ClinicAll": { "main": [[{ "node": "Montar query", "type": "main", "index": 0 }]] },
    "Montar query": { "main": [[{ "node": "GET /profissionais", "type": "main", "index": 0 }]] },
    "GET /profissionais": { "main": [[{ "node": "Classificar resultado", "type": "main", "index": 0 }]] }
  }
}

Guarde o id retornado como <wf-profissional>.

  • Step 2: Validar

n8n_validate_workflow com {"id": "<wf-profissional>"}. Esperado: sem erros.


Task 5: Sub-workflow "MCP Server - ClinicAll - procedimento"

Interfaces:

  • Produces: workflow MCP Server - ClinicAll - procedimento, recebe nome e grp (ambos opcionais), retorna o corpo cru de GET /datasnap/rest/TEntityController/procedimentos ou erro classificado.

  • Step 1: Criar o workflow

{
  "name": "MCP Server - ClinicAll - procedimento",
  "nodes": [
    {
      "id": "trigger",
      "name": "Execute Workflow Trigger",
      "type": "n8n-nodes-base.executeWorkflowTrigger",
      "typeVersion": 1.2,
      "position": [0, 0],
      "parameters": {
        "inputSource": "workflowInputs",
        "workflowInputs": {
          "values": [
            { "name": "nome", "type": "string" },
            { "name": "grp", "type": "string" }
          ]
        }
      }
    },
    {
      "id": "config",
      "name": "Config ClinicAll",
      "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": "http://SUBSTITUIR-PELO-HOST-REAL-DA-CLINICA:PORTA" }
          ]
        }
      }
    },
    {
      "id": "montar-query",
      "name": "Montar query",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [520, 0],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const input = $json;\nconst queryParams = {};\nif (input.nome) queryParams.nome = input.nome;\nif (input.grp) queryParams.grp = input.grp;\n\nreturn {\n  json: {\n    baseUrl: input.baseUrl,\n    queryParams,\n  },\n};"
      }
    },
    {
      "id": "get-procedimentos",
      "name": "GET /procedimentos",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [780, 0],
      "parameters": {
        "method": "GET",
        "url": "={{ $json.baseUrl }}/datasnap/rest/TEntityController/procedimentos",
        "authentication": "none",
        "sendQuery": true,
        "specifyQuery": "json",
        "jsonQuery": "={{ JSON.stringify($json.queryParams) }}",
        "options": {
          "response": { "response": { "fullResponse": true, "neverError": true, "responseFormat": "autodetect" } },
          "timeout": 15000
        }
      }
    },
    {
      "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;\n\nfunction classifyError(status, context) {\n  if (status === 401 || status === 403) {\n    return { code: \"AUTHENTICATION_ERROR\", message: `Falha de autenticação na API do ClinicAll ao ${context} (status ${status}).` };\n  }\n  if (status === 404) {\n    return { code: \"NOT_FOUND\", message: `Recurso não encontrado na API do ClinicAll ao ${context} (status ${status}).` };\n  }\n  if (status === 400 || status === 422) {\n    return { code: \"VALIDATION_ERROR\", message: `A API do ClinicAll rejeitou os dados enviados ao ${context} (status ${status}).` };\n  }\n  return { code: \"CLINICALL_API_ERROR\", message: `A API do ClinicAll retornou um erro inesperado ao ${context} (status ${status}).` };\n}\n\nif (status < 200 || status >= 300) {\n  return { json: { success: false, error: classifyError(status, \"consultar procedimentos\") } };\n}\n\nreturn { json: { success: true, data: rawBody } };"
      }
    }
  ],
  "connections": {
    "Execute Workflow Trigger": { "main": [[{ "node": "Config ClinicAll", "type": "main", "index": 0 }]] },
    "Config ClinicAll": { "main": [[{ "node": "Montar query", "type": "main", "index": 0 }]] },
    "Montar query": { "main": [[{ "node": "GET /procedimentos", "type": "main", "index": 0 }]] },
    "GET /procedimentos": { "main": [[{ "node": "Classificar resultado", "type": "main", "index": 0 }]] }
  }
}

Guarde o id retornado como <wf-procedimento>.

  • Step 2: Validar

n8n_validate_workflow com {"id": "<wf-procedimento>"}. Esperado: sem erros.


Task 6: Sub-workflow "MCP Server - ClinicAll - paciente"

Interfaces:

  • Produces: workflow MCP Server - ClinicAll - paciente, recebe busca (nome ou id do paciente), retorna o corpo cru de GET /datasnap/rest/TPatientController/paciente ou erro classificado.

  • Step 1: Criar o workflow

{
  "name": "MCP Server - ClinicAll - paciente",
  "nodes": [
    {
      "id": "trigger",
      "name": "Execute Workflow Trigger",
      "type": "n8n-nodes-base.executeWorkflowTrigger",
      "typeVersion": 1.2,
      "position": [0, 0],
      "parameters": {
        "inputSource": "workflowInputs",
        "workflowInputs": { "values": [{ "name": "busca", "type": "string" }] }
      }
    },
    {
      "id": "config",
      "name": "Config ClinicAll",
      "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": "http://SUBSTITUIR-PELO-HOST-REAL-DA-CLINICA:PORTA" }
          ]
        }
      }
    },
    {
      "id": "montar-query",
      "name": "Montar query",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [520, 0],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const input = $json;\nreturn {\n  json: {\n    baseUrl: input.baseUrl,\n    queryParams: { busca: input.busca },\n  },\n};"
      }
    },
    {
      "id": "get-paciente",
      "name": "GET /paciente",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [780, 0],
      "parameters": {
        "method": "GET",
        "url": "={{ $json.baseUrl }}/datasnap/rest/TPatientController/paciente",
        "authentication": "none",
        "sendQuery": true,
        "specifyQuery": "json",
        "jsonQuery": "={{ JSON.stringify($json.queryParams) }}",
        "options": {
          "response": { "response": { "fullResponse": true, "neverError": true, "responseFormat": "autodetect" } },
          "timeout": 15000
        }
      }
    },
    {
      "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;\n\nfunction classifyError(status, context) {\n  if (status === 401 || status === 403) {\n    return { code: \"AUTHENTICATION_ERROR\", message: `Falha de autenticação na API do ClinicAll ao ${context} (status ${status}).` };\n  }\n  if (status === 404) {\n    return { code: \"NOT_FOUND\", message: `Recurso não encontrado na API do ClinicAll ao ${context} (status ${status}).` };\n  }\n  if (status === 400 || status === 422) {\n    return { code: \"VALIDATION_ERROR\", message: `A API do ClinicAll rejeitou os dados enviados ao ${context} (status ${status}).` };\n  }\n  return { code: \"CLINICALL_API_ERROR\", message: `A API do ClinicAll retornou um erro inesperado ao ${context} (status ${status}).` };\n}\n\nif (status < 200 || status >= 300) {\n  return { json: { success: false, error: classifyError(status, \"consultar paciente\") } };\n}\n\nreturn { json: { success: true, data: rawBody } };"
      }
    }
  ],
  "connections": {
    "Execute Workflow Trigger": { "main": [[{ "node": "Config ClinicAll", "type": "main", "index": 0 }]] },
    "Config ClinicAll": { "main": [[{ "node": "Montar query", "type": "main", "index": 0 }]] },
    "Montar query": { "main": [[{ "node": "GET /paciente", "type": "main", "index": 0 }]] },
    "GET /paciente": { "main": [[{ "node": "Classificar resultado", "type": "main", "index": 0 }]] }
  }
}

Guarde o id retornado como <wf-paciente>.

  • Step 2: Validar

n8n_validate_workflow com {"id": "<wf-paciente>"}. Esperado: sem erros.


Task 7: Sub-workflow "MCP Server - ClinicAll - consultar_horarios"

Interfaces:

  • Produces: workflow MCP Server - ClinicAll - consultar_horarios, recebe idUnidade, idProfissional (obrigatórios), meses, procedimento, especialidade, convenio (opcionais), retorna o corpo cru de GET /datasnap/rest/TCalendarController/disponibilidade/{idUnidade}/{idProfissional} ou erro classificado.

  • Step 1: Criar o workflow

{
  "name": "MCP Server - ClinicAll - consultar_horarios",
  "nodes": [
    {
      "id": "trigger",
      "name": "Execute Workflow Trigger",
      "type": "n8n-nodes-base.executeWorkflowTrigger",
      "typeVersion": 1.2,
      "position": [0, 0],
      "parameters": {
        "inputSource": "workflowInputs",
        "workflowInputs": {
          "values": [
            { "name": "idUnidade", "type": "string" },
            { "name": "idProfissional", "type": "string" },
            { "name": "meses", "type": "string" },
            { "name": "procedimento", "type": "string" },
            { "name": "especialidade", "type": "string" },
            { "name": "convenio", "type": "string" }
          ]
        }
      }
    },
    {
      "id": "config",
      "name": "Config ClinicAll",
      "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": "http://SUBSTITUIR-PELO-HOST-REAL-DA-CLINICA:PORTA" }
          ]
        }
      }
    },
    {
      "id": "montar-url-query",
      "name": "Montar URL e query",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [520, 0],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const input = $json;\nconst queryParams = {};\nif (input.meses) queryParams.m = input.meses;\nif (input.procedimento) queryParams.p = input.procedimento;\nif (input.especialidade) queryParams.e = input.especialidade;\nif (input.convenio) queryParams.c = input.convenio;\n\nreturn {\n  json: {\n    url: `${input.baseUrl}/datasnap/rest/TCalendarController/disponibilidade/${input.idUnidade}/${input.idProfissional}`,\n    queryParams,\n  },\n};"
      }
    },
    {
      "id": "get-disponibilidade",
      "name": "GET /disponibilidade",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [780, 0],
      "parameters": {
        "method": "GET",
        "url": "={{ $json.url }}",
        "authentication": "none",
        "sendQuery": true,
        "specifyQuery": "json",
        "jsonQuery": "={{ JSON.stringify($json.queryParams) }}",
        "options": {
          "response": { "response": { "fullResponse": true, "neverError": true, "responseFormat": "autodetect" } },
          "timeout": 15000
        }
      }
    },
    {
      "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;\n\nfunction classifyError(status, context) {\n  if (status === 401 || status === 403) {\n    return { code: \"AUTHENTICATION_ERROR\", message: `Falha de autenticação na API do ClinicAll ao ${context} (status ${status}).` };\n  }\n  if (status === 404) {\n    return { code: \"NOT_FOUND\", message: `Recurso não encontrado na API do ClinicAll ao ${context} (status ${status}).` };\n  }\n  if (status === 400 || status === 422) {\n    return { code: \"VALIDATION_ERROR\", message: `A API do ClinicAll rejeitou os dados enviados ao ${context} (status ${status}).` };\n  }\n  return { code: \"CLINICALL_API_ERROR\", message: `A API do ClinicAll retornou um erro inesperado ao ${context} (status ${status}).` };\n}\n\nif (status < 200 || status >= 300) {\n  return { json: { success: false, error: classifyError(status, \"consultar horários disponíveis\") } };\n}\n\nreturn { json: { success: true, data: rawBody } };"
      }
    }
  ],
  "connections": {
    "Execute Workflow Trigger": { "main": [[{ "node": "Config ClinicAll", "type": "main", "index": 0 }]] },
    "Config ClinicAll": { "main": [[{ "node": "Montar URL e query", "type": "main", "index": 0 }]] },
    "Montar URL e query": { "main": [[{ "node": "GET /disponibilidade", "type": "main", "index": 0 }]] },
    "GET /disponibilidade": { "main": [[{ "node": "Classificar resultado", "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.


Task 8: Sub-workflow "MCP Server - ClinicAll - agendar"

Interfaces:

  • Produces: workflow MCP Server - ClinicAll - agendar, recebe idAgenda (obrigatório, vem de consultar_horarios), idUnidade, idPaciente, idProfissional, idProcedimento, dataAgenda, horaAgenda, idConvenio, carteiraConvenio, validadeCarteiraConvenio, usuario (obrigatório), senhaAutorizacao (opcional), retorna { success: true, data: {...} } ou { success: false, error: {...} }.

  • Step 1: Criar o workflow

{
  "name": "MCP Server - ClinicAll - 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": "idAgenda", "type": "string" },
            { "name": "idUnidade", "type": "string" },
            { "name": "idPaciente", "type": "string" },
            { "name": "idProfissional", "type": "string" },
            { "name": "idProcedimento", "type": "string" },
            { "name": "dataAgenda", "type": "string" },
            { "name": "horaAgenda", "type": "string" },
            { "name": "idConvenio", "type": "string" },
            { "name": "carteiraConvenio", "type": "string" },
            { "name": "validadeCarteiraConvenio", "type": "string" },
            { "name": "usuario", "type": "string" },
            { "name": "senhaAutorizacao", "type": "string" }
          ]
        }
      }
    },
    {
      "id": "config",
      "name": "Config ClinicAll",
      "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": "http://SUBSTITUIR-PELO-HOST-REAL-DA-CLINICA:PORTA" }
          ]
        }
      }
    },
    {
      "id": "montar-corpo",
      "name": "Montar corpo",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [520, 0],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const input = $json;\nconst requestBody = {\n  idAgenda: input.idAgenda,\n  idUnidade: input.idUnidade,\n  idPaciente: input.idPaciente,\n  idProfissional: input.idProfissional,\n  idProcedimento: input.idProcedimento,\n  dataAgenda: input.dataAgenda,\n  horaAgenda: input.horaAgenda,\n  idConvenio: input.idConvenio,\n  carteiraConvenio: input.carteiraConvenio,\n  validadeCarteiraConvenio: input.validadeCarteiraConvenio,\n  usuario: input.usuario,\n};\nif (input.senhaAutorizacao) requestBody.senhaAutorizacao = input.senhaAutorizacao;\n\nreturn {\n  json: {\n    baseUrl: input.baseUrl,\n    requestBody,\n  },\n};"
      }
    },
    {
      "id": "put-agendamento",
      "name": "PUT /agendamento",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [780, 0],
      "parameters": {
        "method": "PUT",
        "url": "={{ $json.baseUrl }}/datasnap/rest/TCalendarController/agendamento",
        "authentication": "none",
        "sendBody": true,
        "contentType": "json",
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json.requestBody) }}",
        "options": {
          "response": { "response": { "fullResponse": true, "neverError": true, "responseFormat": "autodetect" } },
          "timeout": 15000
        }
      }
    },
    {
      "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, context) {\n  if (status === 401 || status === 403) {\n    return { code: \"AUTHENTICATION_ERROR\", message: `Falha de autenticação na API do ClinicAll ao ${context} (status ${status}).` };\n  }\n  if (status === 404) {\n    return { code: \"NOT_FOUND\", message: `Recurso não encontrado na API do ClinicAll ao ${context} (status ${status}).` };\n  }\n  if (status === 400 || status === 422) {\n    return { code: \"VALIDATION_ERROR\", message: `A API do ClinicAll rejeitou os dados enviados ao ${context} (status ${status}).` };\n  }\n  return { code: \"CLINICALL_API_ERROR\", message: `A API do ClinicAll retornou um erro inesperado ao ${context} (status ${status}).` };\n}\n\nif (status < 200 || status >= 300) {\n  return { json: { success: false, error: classifyError(status, \"criar o agendamento\") } };\n}\n\nlet parsed = null;\ntry {\n  parsed = JSON.parse(bodyText);\n} catch (e) {\n  parsed = null;\n}\n\nconst idAgendaConfirmado = parsed && (parsed.idAgenda !== undefined && parsed.idAgenda !== null) ? String(parsed.idAgenda) : null;\n\nif (idAgendaConfirmado) {\n  return {\n    json: {\n      success: true,\n      data: {\n        id: idAgendaConfirmado,\n        situacaoAgenda: typeof parsed.situacaoAgenda === \"string\" ? parsed.situacaoAgenda : undefined,\n        estadoAgenda: typeof parsed.estadoAgenda === \"string\" ? parsed.estadoAgenda : undefined,\n        responseMappingPending: false,\n      },\n    },\n  };\n}\n\nconst input = $('Montar corpo').item.json;\nreturn {\n  json: {\n    success: true,\n    data: {\n      id: input.requestBody.idAgenda,\n      dataAgenda: input.requestBody.dataAgenda,\n      horaAgenda: input.requestBody.horaAgenda,\n      responseMappingPending: true,\n    },\n  },\n};"
      }
    }
  ],
  "connections": {
    "Execute Workflow Trigger": { "main": [[{ "node": "Config ClinicAll", "type": "main", "index": 0 }]] },
    "Config ClinicAll": { "main": [[{ "node": "Montar corpo", "type": "main", "index": 0 }]] },
    "Montar corpo": { "main": [[{ "node": "PUT /agendamento", "type": "main", "index": 0 }]] },
    "PUT /agendamento": { "main": [[{ "node": "Classificar resultado", "type": "main", "index": 0 }]] }
  }
}

Guarde o id retornado como <wf-agendar>.

  • Step 2: Validar

n8n_validate_workflow com {"id": "<wf-agendar>"}. Esperado: sem erros.


Task 9: Workflow "MCP Server - ClinicAll" (o servidor)

Interfaces:

  • Consumes: <wf-unidade> (Task 1), <wf-especialidade> (Task 2), <wf-convenio> (Task 3), <wf-profissional> (Task 4), <wf-procedimento> (Task 5), <wf-paciente> (Task 6), <wf-consultar-horarios> (Task 7), <wf-agendar> (Task 8).

  • Produces: endpoint MCP em https://devn8n.gtigrupo.com.br/mcp/mcp-server-clinicall.

  • Step 1: Criar o workflow com o trigger e os oito nodes de tool

{
  "name": "MCP Server - ClinicAll",
  "nodes": [
    {
      "id": "mcp-trigger",
      "name": "MCP Server Trigger",
      "type": "@n8n/n8n-nodes-langchain.mcpTrigger",
      "typeVersion": 2,
      "position": [0, 0],
      "parameters": { "path": "mcp-server-clinicall", "authentication": "none" }
    },
    {
      "id": "tool-unidade",
      "name": "unidade",
      "type": "@n8n/n8n-nodes-langchain.toolWorkflow",
      "typeVersion": 2.2,
      "position": [260, -280],
      "parameters": {
        "description": "Lista as unidades físicas da clínica, com nome opcional para filtrar. Sem parâmetros obrigatórios.",
        "source": "database",
        "workflowId": { "value": "<wf-unidade>", "mode": "id" }
      }
    },
    {
      "id": "tool-especialidade",
      "name": "especialidade",
      "type": "@n8n/n8n-nodes-langchain.toolWorkflow",
      "typeVersion": 2.2,
      "position": [260, -200],
      "parameters": {
        "description": "Lista as especialidades médicas disponíveis, com nome opcional para filtrar. Sem parâmetros obrigatórios.",
        "source": "database",
        "workflowId": { "value": "<wf-especialidade>", "mode": "id" }
      }
    },
    {
      "id": "tool-convenio",
      "name": "convenio",
      "type": "@n8n/n8n-nodes-langchain.toolWorkflow",
      "typeVersion": 2.2,
      "position": [260, -120],
      "parameters": {
        "description": "Lista os convênios aceitos pela clínica, com nome opcional para filtrar. Sem parâmetros obrigatórios.",
        "source": "database",
        "workflowId": { "value": "<wf-convenio>", "mode": "id" }
      }
    },
    {
      "id": "tool-profissional",
      "name": "profissional",
      "type": "@n8n/n8n-nodes-langchain.toolWorkflow",
      "typeVersion": 2.2,
      "position": [260, -40],
      "parameters": {
        "description": "Lista os profissionais/médicos, com nome opcional para filtrar. Já retorna as especialidades e horários de atendimento de cada um.",
        "source": "database",
        "workflowId": { "value": "<wf-profissional>", "mode": "id" }
      }
    },
    {
      "id": "tool-procedimento",
      "name": "procedimento",
      "type": "@n8n/n8n-nodes-langchain.toolWorkflow",
      "typeVersion": 2.2,
      "position": [260, 40],
      "parameters": {
        "description": "Lista os procedimentos disponíveis, com nome e/ou grupo opcionais para filtrar. Sem parâmetros obrigatórios.",
        "source": "database",
        "workflowId": { "value": "<wf-procedimento>", "mode": "id" }
      }
    },
    {
      "id": "tool-paciente",
      "name": "paciente",
      "type": "@n8n/n8n-nodes-langchain.toolWorkflow",
      "typeVersion": 2.2,
      "position": [260, 120],
      "parameters": {
        "description": "Busca um paciente já cadastrado por nome ou id.",
        "source": "database",
        "workflowId": { "value": "<wf-paciente>", "mode": "id" }
      }
    },
    {
      "id": "tool-consultar-horarios",
      "name": "consultar_horarios",
      "type": "@n8n/n8n-nodes-langchain.toolWorkflow",
      "typeVersion": 2.2,
      "position": [260, 200],
      "parameters": {
        "description": "Consulta horários disponíveis para uma unidade e profissional específicos (ambos obrigatórios, códigos obtidos via as tools unidade e profissional). Retorna idAgenda e horaAgenda de cada horário livre — o idAgenda é usado depois na tool agendar.",
        "source": "database",
        "workflowId": { "value": "<wf-consultar-horarios>", "mode": "id" }
      }
    },
    {
      "id": "tool-agendar",
      "name": "agendar",
      "type": "@n8n/n8n-nodes-langchain.toolWorkflow",
      "typeVersion": 2.2,
      "position": [260, 280],
      "parameters": {
        "description": "Cria o agendamento real na clínica, conciliando um idAgenda livre já obtido via consultar_horarios. Use somente depois de já ter resolvido, com as outras tools, todos os códigos necessários (paciente, profissional, unidade, procedimento, convênio).",
        "source": "database",
        "workflowId": { "value": "<wf-agendar>", "mode": "id" }
      }
    }
  ],
  "connections": {
    "unidade": { "ai_tool": [[{ "node": "MCP Server Trigger", "type": "ai_tool", "index": 0 }]] },
    "especialidade": { "ai_tool": [[{ "node": "MCP Server Trigger", "type": "ai_tool", "index": 0 }]] },
    "convenio": { "ai_tool": [[{ "node": "MCP Server Trigger", "type": "ai_tool", "index": 0 }]] },
    "profissional": { "ai_tool": [[{ "node": "MCP Server Trigger", "type": "ai_tool", "index": 0 }]] },
    "procedimento": { "ai_tool": [[{ "node": "MCP Server Trigger", "type": "ai_tool", "index": 0 }]] },
    "paciente": { "ai_tool": [[{ "node": "MCP Server Trigger", "type": "ai_tool", "index": 0 }]] },
    "consultar_horarios": { "ai_tool": [[{ "node": "MCP Server Trigger", "type": "ai_tool", "index": 0 }]] },
    "agendar": { "ai_tool": [[{ "node": "MCP Server Trigger", "type": "ai_tool", "index": 0 }]] }
  }
}

Substitua os 8 <wf-...> pelos IDs reais das Tasks 1-8.

Guarde o id deste workflow como <wf-mcp-server-clinicall>.

  • Step 2: Mapear os parâmetros de cada tool na UI (passo manual — resourceMapper não é confiável via API)

Abrir os 7 nodes de tool com parâmetro (todos exceto unidade... na verdade unidade também tem nome opcional — todos os 8 têm ao menos um campo) na UI do n8n. Em "Workflow Inputs", mapear cada campo para uma expressão $fromAI(...) com descrição clara pro modelo. Exemplo para agendar:

idAgenda: ={{ $fromAI('idAgenda', 'Id do horário livre escolhido, retornado por consultar_horarios', 'string') }}
idUnidade: ={{ $fromAI('idUnidade', 'Código da unidade retornado pela tool unidade', 'string') }}
idPaciente: ={{ $fromAI('idPaciente', 'Código do paciente retornado pela tool paciente', 'string') }}
idProfissional: ={{ $fromAI('idProfissional', 'Código do profissional retornado pela tool profissional', 'string') }}
idProcedimento: ={{ $fromAI('idProcedimento', 'Código do procedimento retornado pela tool procedimento', 'string') }}
dataAgenda: ={{ $fromAI('dataAgenda', 'Data do agendamento, no mesmo formato retornado por consultar_horarios', 'string') }}
horaAgenda: ={{ $fromAI('horaAgenda', 'Hora do agendamento, no mesmo formato retornado por consultar_horarios', 'string') }}
idConvenio: ={{ $fromAI('idConvenio', 'Código do convênio retornado pela tool convenio', 'string') }}
carteiraConvenio: ={{ $fromAI('carteiraConvenio', 'Número da carteirinha do convênio do paciente', 'string') }}
validadeCarteiraConvenio: ={{ $fromAI('validadeCarteiraConvenio', 'Data de validade da carteirinha do convênio', 'string') }}
usuario: ={{ $fromAI('usuario', 'Nome de identificação de quem está realizando o agendamento (ex.: nome do assistente/atendente virtual)', 'string') }}
senhaAutorizacao: ={{ $fromAI('senhaAutorizacao', 'Senha de autorização do agendamento, se aplicável (opcional)', 'string') }}

Mesmo padrão pros demais 6 nodes com parâmetro, usando os nomes de campo já definidos nas Tasks 1-7 (nome para os de catálogo, grp adicional em procedimento, busca em paciente, idUnidade/idProfissional/meses/procedimento/especialidade/convenio em consultar_horarios).

  • Step 3: Validar (sem ativar)

n8n_validate_workflow com {"id": "<wf-mcp-server-clinicall>"}. Esperado: sem erros. Não ativar nenhum dos 9 workflows nesta etapa — sem clínica real, sem workflow de agente cliente, e sem o mecanismo de autenticação resolvido (ver Global Constraints).


Task 10: Revisão final e commit

Interfaces:

  • Consumes: workflows das Tasks 1-9.

  • Step 1: Validação estrutural final

Rodar n8n_validate_workflow nos 9 IDs com {"options": {"profile": "ai-friendly"}}. Esperado: sem erros em nenhum.

  • Step 2: Atualizar a spec com os IDs reais

Editar docs/superpowers/specs/2026-08-11-mcp-server-nativo-n8n-clinicall-design.md, adicionando ao final uma seção "Status desta etapa" com os 9 IDs de workflow criados, o path do endpoint MCP (mcp-server-clinicall), e reafirmando as duas pendências reais (autenticação e verbo HTTP do agendamento) como não resolvidas.

  • Step 3: Commit
cd "D:\PROJETOS\KLINGO-CLINICAL-SYSLIFE-GCHAT-AGENDAMENTO"
git add "docs/superpowers/specs/2026-08-11-mcp-server-nativo-n8n-clinicall-design.md"
git commit -m "docs: registra IDs dos workflows do MCP Server nativo (ClinicAll)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>"

Observação sobre execução

Mesmo padrão dos planos anteriores (Z-PRO, Klingo, SysLife): tasks são chamadas de API ao n8n, não commits de código. Sem clínica ClinicAll real e sem mecanismo de autenticação resolvido, o resultado desta etapa é estrutura pronta e validada, não um fluxo testado de ponta a ponta — isso é intencional (ver Global Constraints), não um passo pulado por engano. As Tasks 1-4 (unidade, especialidade, convenio, profissional) têm esqueleto quase idêntico entre si — proposital, mesmo padrão do manual, não duplicação acidental a refatorar.