# 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 ` 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//` (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 ```