Files
GustavoandClaude Sonnet 5 257afe98c0 Snapshot: gti-scheduling-mcp (Z-PRO + Klingo adapters) e workflow n8n de agendamento funcionando de ponta a ponta
- gti-scheduling-mcp: MCP server com ZProAdapter e KlingoAdapter (agendar() completo pros dois)
- Workflow n8n 'Agendamento - GChat - Z-PRO': webhook -> AI Agent -> MCP -> Z-PRO, testado com sucesso real
- Documentacao: specs, plano, runbook de problemas reais e export do workflow

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 17:23:10 -03:00
..

gti-scheduling-mcp

Objetivo do projeto

gti-scheduling-mcp é um MCP Server responsável exclusivamente por realizar agendamentos em sistemas de clínicas.

O MCP nunca conhece detalhes de nenhuma API específica. Toda a integração com um sistema de clínica (Z-Pro, Klingo, SysLife, ClinicAll, etc.) é responsabilidade exclusiva do respectivo Adapter, que implementa uma interface comum e genérica.

Neste MVP existem apenas duas ferramentas (tools) expostas via MCP:

  • consultar_horarios — consulta horários disponíveis para agendamento.
  • agendar — realiza um agendamento em um horário disponível.

Nenhuma outra ferramenta, regra de negócio, integração HTTP, banco de dados ou IA é implementada nesta etapa. Este repositório representa apenas o esqueleto inicial do projeto.

Estrutura das pastas

src/
  adapters/
    scheduling-adapter.interface.ts   # Contrato comum a todos os Adapters
    adapter-factory.ts                # Fábrica que resolve o Adapter a usar
    zpro/
      zpro.adapter.ts                 # Adapter do sistema Z-Pro
      zpro.types.ts                   # Tipos internos (corpo da requisição verificado)
      zpro.mapper.ts                  # Mapeamento genérico -> corpo da requisição Z-PRO
      zpro-http-client.ts             # Cliente HTTP (POST appointment/create)
      zpro.errors.ts                  # Erros tipados e classificação de status HTTP
    klingo/
      klingo.adapter.ts               # Adapter do sistema Klingo
      klingo.types.ts                 # Tipos internos (schemas verificados no Swagger)
      klingo.mapper.ts                # Mapeamento genérico <-> requisições/respostas Klingo
      klingo-http-client.ts           # Cliente HTTP (identificar, horarios, reservar)
      klingo.errors.ts                # Erros tipados e classificação de status HTTP

  tools/
    consultar-horarios.tool.ts        # Definição da tool "consultar_horarios"
    agendar.tool.ts                   # Definição da tool "agendar"
    mcp-response.ts                   # Helpers de resposta MCP (sucesso/erro)

  server/
    mcp.server.ts                     # Criação/registro do servidor MCP

  types/
    scheduling.types.ts               # Tipos genéricos de domínio

  config/
    env.config.ts                     # Leitura/validação de variáveis de ambiente
    config.types.ts                   # Tipos de configuração (Z-Pro e genérico)

  diagnostics/
    sanitize.ts                       # Sanitização recursiva de respostas
    analyze.ts                        # Identificação da estrutura de respostas
    client.ts                         # Cliente HTTP mínimo (GET + timeout)
    response.ts                       # Tratamento de resposta JSON/não JSON

  index.ts                            # Ponto de entrada da aplicação

scripts/
  diagnose-zpro-list.ts               # CLI temporário de descoberta do contrato da API

test/
  config.test.ts                      # Testes da validação de configuração
  adapter-factory.test.ts             # Testes da AdapterFactory
  tools.test.ts                       # Testes do tratamento de erro das ferramentas
  zpro-adapter.test.ts                # Testes do ZProAdapter real (mocks de fetch)
  klingo-adapter.test.ts              # Testes do KlingoAdapter real (mocks de fetch)
  sanitize.test.ts                    # Testes da sanitização recursiva
  analyze.test.ts                     # Testes da identificação de estrutura
  diagnose-client.test.ts             # Testes do client de diagnóstico e respostas

docs/
  zpro-api-mapping.md                 # Contrato verificado da API do Z-PRO
  klingo-openapi.json                 # Swagger/OpenAPI público da API do Klingo

Fluxo do sistema

  1. Um cliente MCP invoca uma das ferramentas registradas (consultar_horarios ou agendar).
  2. A ferramenta (tools/) recebe a chamada, mas não contém regra de negócio — sua responsabilidade é apenas definir o contrato de entrada e delegar a execução.
  3. A execução é delegada ao Adapter apropriado, obtido através da adapter-factory.ts, que recebe a configuração validada e resolve o Adapter correto para o provider configurado.
  4. O Adapter (ex.: ZProAdapter) é responsável por toda a comunicação com o sistema real da clínica, respeitando a interface SchedulingAdapter.
  5. O resultado retorna ao cliente MCP no formato genérico definido em types/scheduling.types.ts.

As ferramentas MCP capturam qualquer erro lançado pelo Adapter e devolvem uma resposta estruturada (success: false + código/mensagem), sem encerrar o servidor.

Status do ZProAdapter

  • consultarHorarios(): decisão de produto — o Z-PRO não expõe nenhum endpoint de disponibilidade de horários (ver docs/zpro-api-mapping.md). O método sempre lança um erro claro (ZProApiError com código NOT_SUPPORTED); não há solução alternativa implementada (não se tenta inferir disponibilidade a partir da lista de agendamentos existentes).

  • agendar(): a chamada HTTP real (POST appointment/create) é implementada de ponta a ponta — autenticação, URL, corpo da requisição e classificação de erros HTTP (401/403/404/400/422/500, timeout, erro de rede).

    O mapeamento da resposta de sucesso foi verificado por um teste manual único e controlado em 09/08/2026, contra uma instância real do Z-PRO (ver docs/zpro-api-mapping.md, seção "Atualização — resposta real de sucesso capturada"). Quando o corpo bate com o envelope observado ({success: true, data: {id, startAt, endAt, status}}), o Appointment é construído com o id real retornado pela API — não é mais um placeholder.

    Mesmo assim, qualquer HTTP 2xx continua sendo tratado como sucesso operacional — o adapter não lança erro quando o corpo foge desse envelope (formato diferente, texto simples, vazio, ou JSON malformado). Só existe um exemplo real capturado até agora (só cobre o caminho de sucesso com status: "pending"); reportar como falha uma operação que o provider aceitou (e que pode já ter sido persistida) induziria um consumidor (agente/n8n) a repeti-la, arriscando um agendamento duplicado. Nesse caso, agendar() retorna um Appointment construído apenas com os dados já conhecidos do próprio input, com id ausente e responseMappingPending: true. Consumidores devem tratar responseMappingPending: true como "aceito, detalhes incompletos" — nunca como motivo para reenviar a operação. Apenas respostas HTTP fora do intervalo 2xx continuam classificadas como erro (401/403/404/400/422/500, timeout, erro de rede).

    Opcionalmente, new ZProAdapter(config, { onResponseMetadata }) aceita um callback de diagnóstico chamado após cada resposta do Z-PRO, com metadados seguros (status, content-type, presença de corpo, formato aparente) — nunca o corpo, o token ou dados do paciente. Sem esse callback, nada é registrado (por padrão, para nunca escrever em stdout e corromper o transporte stdio do MCP).

Status do KlingoAdapter

Baseado inteiramente em docs/klingo-openapi.json (Swagger público da API do Klingo — bem mais completo que o do Z-PRO: 41 endpoints documentados, incluindo consulta de disponibilidade real).

  • consultarHorarios(): chama GET /agenda/horarios de verdade, autenticado por X-APP-TOKEN. A API confirma a estrutura externa da resposta (procedimento, horarios[] com id/data/profissional/ unidade), mas o formato interno de cada horário específico não está documentado no Swagger — é descrito apenas como um objeto genérico, sem properties/items. Sem essa informação não é possível montar AvailableSlot.startDateTime/endDateTime reais sem inventar nomes de campo. Por isso este método sempre lança KlingoApiError com código SLOT_MAPPING_UNAVAILABLE quando a API responde com sucesso — é uma lacuna de contrato conhecida, não uma falha técnica, e não deve ser tratada como motivo para retry. Assim que uma resposta real de GET /agenda/horarios for capturada, klingo.adapter.ts deve ganhar o mapeamento completo.

  • agendar(): implementado de ponta a ponta.

    1. Identifica o paciente via POST /paciente/identificar (X-APP-TOKEN), usando patient.phone + patient.document (como CPF) — ou apenas_telefone: true quando não há document. Não cria nenhum dado no Klingo, então erros aqui (inclusive resposta 2xx sem access_token válido) são reportados normalmente, sem risco de duplicidade.
    2. Reserva o horário via POST /agenda/reservar, autenticado pelo Bearer de sessão obtido no passo anterior, enviando procedimento (de input.specialtyId) e id (de input.slotId) — ambos obrigatórios pela API, validados antes de qualquer chamada de rede. Envia também confirmado preenchido, para que a marcação já seja feita como confirmada em vez de uma reserva temporária de 10 minutos — comportamento documentado literalmente no Swagger ("Se for passado um texto, a marcação já irá ser feita como confirmada").
    3. O schema de sucesso de reservar (reserva) é documentado (id, data, hora, profissional) — diferente do Z-PRO, a resposta é mapeada de verdade para o Appointment genérico, com status: "confirmed". Mesmo assim, qualquer HTTP 2xx continua sendo tratado como sucesso operacional mesmo que o corpo fuja do schema documentado (JSON sem id numérico, corpo vazio, texto, JSON malformado) — mesma proteção contra duplicidade do ZProAdapter: nesse caso, retorna um Appointment com responseMappingPending: true em vez de lançar erro.

    Variáveis de ambiente: apenas KLINGO_BASE_URL e KLINGO_APP_TOKEN (ver seção Configuração). O token de sessão do paciente não é uma variável de ambiente — é obtido em tempo de execução, por paciente, e descartado a cada chamada (sem cache, por simplicidade neste MVP).

Configuração

A configuração é lida exclusivamente por variáveis de ambiente (módulo src/config/env.config.ts), conforme o arquivo de exemplo .env.example. Cada provider só exige suas próprias variáveis — as do provider não selecionado em SCHEDULING_PROVIDER não são exigidas.

Variável Obrigatória Descrição
SCHEDULING_PROVIDER Não (padrão zpro) Provider de agendamento (zpro | klingo)
ZPRO_BASE_URL Sim, se SCHEDULING_PROVIDER=zpro URL base da API do Z-PRO
ZPRO_API_TOKEN Sim, se SCHEDULING_PROVIDER=zpro Token de autenticação da API do Z-PRO
ZPRO_API_ID Sim, se SCHEDULING_PROVIDER=zpro Identificador da API (ApiID) usado nas rotas externas
ZPRO_INSTANCE_ID Não Identificador da instância no Z-PRO (mantido por compatibilidade)
KLINGO_BASE_URL Sim, se SCHEDULING_PROVIDER=klingo URL base da API do Klingo
KLINGO_APP_TOKEN Sim, se SCHEDULING_PROVIDER=klingo Token de aplicação do Klingo, enviado no header X-APP-TOKEN

O token de sessão do paciente do Klingo (Authorization: Bearer, obtido via /externo/login ou /paciente/identificar) não é uma variável de ambiente — não é um segredo fixo de configuração, e sim algo obtido em tempo de execução por paciente. Ver docs/klingo-openapi.json.

Com configuração inválida, a aplicação encerra a inicialização com uma mensagem clara, sem expor nenhum token em mensagens de erro ou logs. Nunca crie um arquivo .env real com credenciais; o .env está no .gitignore.

Script de diagnóstico da API Z-PRO

O script scripts/diagnose-zpro-list.ts consulta apenas o endpoint de listagem de agendamentos do Z-PRO (GET) para descobrir o contrato real da resposta, pois a coleção pública do Postman não possui exemplos de resposta. Veja docs/zpro-api-mapping.md.

npm run diagnose:zpro:list

O script:

  • faz uma única requisição GET em {ZPRO_BASE_URL}/v2/api/external/{ZPRO_API_ID}/appointment/list com page=1 e limit=5 (parâmetros conservadores);
  • envia Authorization: Bearer <token> e Accept: application/json;
  • usa timeout de 15 segundos e não executa retry;
  • não chama appointment/create nem modifica dados;
  • imprime informações da requisição (URL sem token, status, content-type, duração), a resposta sanitizada e um resumo estrutural;
  • nunca imprime token, stack trace ou dados pessoais completos dos pacientes (valores sensíveis são mascarados com [REDACTED]).

Em caso de falha (variável ausente, autenticação, ApiID inválido, timeout, rede, resposta não JSON ou status HTTP fora de 2xx), o processo encerra com código de saída 1.

Como adicionar um novo Adapter

  1. Criar uma nova pasta em src/adapters/<nome-do-sistema>/ (ex.: src/adapters/klingo/).
  2. Criar a classe do Adapter (ex.: klingo.adapter.ts) implementando a interface SchedulingAdapter, definida em scheduling-adapter.interface.ts.
  3. Implementar os métodos consultarHorarios() e agendar(), traduzindo os dados específicos do sistema para os tipos genéricos definidos em types/scheduling.types.ts.
  4. Atualizar adapter-factory.ts para que o novo Adapter possa ser selecionado quando necessário.
  5. Nunca expor detalhes do sistema específico (nomes de campos, endpoints, formatos próprios, etc.) fora do próprio Adapter.

Scripts disponíveis

npm run dev               # Executa o servidor em modo desenvolvimento (tsx)
npm run build             # Compila o projeto TypeScript para dist/
npm run start             # Executa o projeto já compilado
npm run test              # Executa os testes unitários (node:test via tsx)
npm run diagnose:zpro:list # Consulta o endpoint de listagem do Z-PRO (leitura)
npm run lint              # Executa o ESLint
npm run format            # Formata o código com Prettier