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>
This commit is contained in:
@@ -0,0 +1,276 @@
|
||||
# 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`.
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
```bash
|
||||
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
|
||||
```
|
||||
Reference in New Issue
Block a user