- 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>
14 KiB
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
- Um cliente MCP invoca uma das ferramentas registradas
(
consultar_horariosouagendar). - 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. - 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. - O Adapter (ex.:
ZProAdapter) é responsável por toda a comunicação com o sistema real da clínica, respeitando a interfaceSchedulingAdapter. - 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 (verdocs/zpro-api-mapping.md). O método sempre lança um erro claro (ZProApiErrorcom códigoNOT_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}}), oAppointmenté construído com oidreal 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 umAppointmentconstruído apenas com os dados já conhecidos do próprio input, comidausente eresponseMappingPending: true. Consumidores devem tratarresponseMappingPending: truecomo "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(): chamaGET /agenda/horariosde verdade, autenticado porX-APP-TOKEN. A API confirma a estrutura externa da resposta (procedimento,horarios[]comid/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, semproperties/items. Sem essa informação não é possível montarAvailableSlot.startDateTime/endDateTimereais sem inventar nomes de campo. Por isso este método sempre lançaKlingoApiErrorcom códigoSLOT_MAPPING_UNAVAILABLEquando 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 deGET /agenda/horariosfor capturada,klingo.adapter.tsdeve ganhar o mapeamento completo. -
agendar(): implementado de ponta a ponta.- Identifica o paciente via
POST /paciente/identificar(X-APP-TOKEN), usandopatient.phone+patient.document(como CPF) — ouapenas_telefone: truequando não hádocument. Não cria nenhum dado no Klingo, então erros aqui (inclusive resposta 2xx semaccess_tokenválido) são reportados normalmente, sem risco de duplicidade. - Reserva o horário via
POST /agenda/reservar, autenticado pelo Bearer de sessão obtido no passo anterior, enviandoprocedimento(deinput.specialtyId) eid(deinput.slotId) — ambos obrigatórios pela API, validados antes de qualquer chamada de rede. Envia tambémconfirmadopreenchido, 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"). - O schema de sucesso de
reservar(reserva) é documentado (id,data,hora,profissional) — diferente do Z-PRO, a resposta é mapeada de verdade para oAppointmentgenérico, comstatus: "confirmed". Mesmo assim, qualquer HTTP 2xx continua sendo tratado como sucesso operacional mesmo que o corpo fuja do schema documentado (JSON semidnumérico, corpo vazio, texto, JSON malformado) — mesma proteção contra duplicidade do ZProAdapter: nesse caso, retorna umAppointmentcomresponseMappingPending: trueem vez de lançar erro.
Variáveis de ambiente: apenas
KLINGO_BASE_URLeKLINGO_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). - Identifica o paciente via
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
GETem{ZPRO_BASE_URL}/v2/api/external/{ZPRO_API_ID}/appointment/listcompage=1elimit=5(parâmetros conservadores); - envia
Authorization: Bearer <token>eAccept: application/json; - usa timeout de 15 segundos e não executa retry;
- não chama
appointment/createnem 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
- Criar uma nova pasta em
src/adapters/<nome-do-sistema>/(ex.:src/adapters/klingo/). - Criar a classe do Adapter (ex.:
klingo.adapter.ts) implementando a interfaceSchedulingAdapter, definida emscheduling-adapter.interface.ts. - Implementar os métodos
consultarHorarios()eagendar(), traduzindo os dados específicos do sistema para os tipos genéricos definidos emtypes/scheduling.types.ts. - Atualizar
adapter-factory.tspara que o novo Adapter possa ser selecionado quando necessário. - 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