medical-mcp-fake
README.md
# medical-mcp-fake
Servidor **MCP (Model Context Protocol)** com transporte **HTTP streamable** e base clínica **fictícia**, para desenvolver e testar agentes enquanto a integração real com o TASI não existe.
> ⚠️ Todos os dados são inventados. Nenhum paciente, CPF, profissional ou resultado de exame aqui corresponde a pessoas ou registros reais.
## Início rápido
```bash
npm install && npm run seed && npm run dev
```
O servidor sobe em `http://localhost:4000/mcp`. Confira com:
```bash
curl -s localhost:4000/health
```
## Tools
| Tool | Entrada | O que devolve |
|---|---|---|
| `search_patients` | `query`, `context?`, `limit?` | Pacientes cujo nome casa com a busca, com os IDs de cada contexto. **Ponto de entrada** das demais tools. |
| `get_inpatient_full` | `inpatient_id` \| `patient_id`, `sections?` | Prontuário completo da internação. |
| `get_patient_profile` | `patient_id` | Cadastro + linha do tempo de todos os atendimentos. |
| `get_encounter_details` | `encounter_id` | Blocos clínicos de um atendimento ambulatorial. |
| `get_ps_patient` | `ps_visit_id` \| `patient_id` | Ficha completa de uma passagem pelo pronto-socorro. |
### Fluxo típico de um agente
```
search_patients("maria") → PAC-0001, internado, INT-0001
├─ get_patient_profile("PAC-0001") → linha do tempo cruzando PS, internação e ambulatório
├─ get_inpatient_full("INT-0001") → prontuário completo da UTI
└─ get_ps_patient("PS-0001") → como o caso chegou ao hospital
```
### Detalhes de comportamento
**`search_patients`** ignora acentos e maiúsculas (`jose` acha `José Carlos Ferreira`) e exige que **todos** os termos apareçam no nome — `maria souza` não traz todas as Marias. Ranqueia nome exato > palavra exata > prefixo > substring. `context` filtra por `internacao`, `ambulatorial`, `ps` ou `todos`.
**`get_inpatient_full`** aceita `sections` para trazer só os blocos que interessam e economizar contexto do agente:
```json
{ "inpatient_id": "INT-0001", "sections": ["analises_ia", "intercorrencias"] }
```
Seções disponíveis: `sinais_vitais`, `laboratorio`, `exames_imagem`, `intercorrencias`, `enfermagem`, `evolucoes_medicas`, `plano_terapeutico`, `notas_round`, `analises_ia`. O cabeçalho da internação e os dados do paciente vêm sempre.
**`get_inpatient_full`** e **`get_ps_patient`** aceitam `patient_id` no lugar do ID específico e resolvem para o registro **ativo** (internação em curso / passagem em andamento) ou, na ausência dele, para o mais recente.
**Erros** — ID inexistente devolve resultado com `isError: true` e mensagem explícita em português, nunca um objeto vazio. O agente consegue distinguir "não encontrei" de "encontrei e está vazio".
## A base fictícia
18 pacientes, 7 internações, 23 atendimentos ambulatoriais e 8 passagens pelo PS. Nomes brasileiros, CID-10, medicações, exames e séries temporais de sinais vitais plausíveis.
A base cobre casos deliberadamente variados: choque séptico em deterioração na UTI, pós-infarto em melhora, ICC compensando, pancreatite pré-cirúrgica, pós-operatório de fratura com delirium, pé diabético infectado, crise asmática em atendimento agora, dor torácica em investigação, e seguimentos ambulatoriais longos (artrite reumatoide, hipotireoidismo, DPOC, demência).
**Pacientes aparecem em mais de um contexto.** `PAC-0001` e `PAC-0006` têm a trilha completa PS → internação (`ps_visit_id` na internação, `internacao_gerada` na ficha do PS); `PAC-0002` tem consultas ambulatoriais anteriores ao infarto que o internou — inclusive um retorno em que faltou, com o teste ergométrico positivo anexado. São os casos que exercitam o cruzamento de histórico pelo agente.
### Regenerar ou editar
Os JSON em `data/` são **gerados** a partir dos casos curados em `scripts/cases/`:
```bash
npm run seed
```
A saída é determinística: os timestamps são ancorados numa data de referência fixa (`REF` em `scripts/seed-utils.ts`) e os valores usam PRNG com seed fixa. Rodar duas vezes produz exatamente o mesmo JSON.
Para adicionar um paciente ou caso, edite os arquivos em `scripts/cases/` e rode `npm run seed`. O gerador valida integridade referencial e IDs duplicados antes de escrever — referência quebrada falha o comando.
## Configuração
| Variável | Padrão | Descrição |
|---|---|---|
| `PORT` | `4000` | Porta HTTP. |
| `HOST` | `0.0.0.0` | Interface de bind. |
| `API_TOKEN` | *(vazio)* | Se definida, `/mcp` exige `Authorization: Bearer <token>`. Vazia = servidor aberto. |
| `DATA_DIR` | `<cwd>/data` | Diretório dos JSON da base. |
`/health` nunca exige token — é o endpoint que o Coolify usa para healthcheck.
## Docker
```bash
docker compose up --build
```
Ou direto:
```bash
docker build -t medical-mcp-fake . && docker run -p 4000:4000 -e API_TOKEN=seu-token medical-mcp-fake
```
### Deploy no Coolify
1. Nova aplicação → **Dockerfile** (ou Docker Compose) apontando para este repositório.
2. Porta exposta: **4000**.
3. Healthcheck: `GET /health`.
4. Variável de ambiente `API_TOKEN` — recomendado, já que o serviço fica exposto na internet.
O endpoint MCP fica em `https://<seu-dominio>/mcp`.
## Conectando um agente
O servidor roda em **modo stateless**: cada `POST /mcp` cria transporte e sessão próprios e os descarta ao final. Não há sessão a manter, porque a base é somente-leitura. `GET` e `DELETE` em `/mcp` respondem `405`.
Exemplo de configuração de cliente MCP:
```json
{
"mcpServers": {
"tasi-fake": {
"type": "http",
"url": "https://<seu-dominio>/mcp",
"headers": { "Authorization": "Bearer <API_TOKEN>" }
}
}
}
```
Chamada crua, para depurar sem cliente MCP:
```bash
curl -s -X POST localhost:4000/mcp -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"search_patients","arguments":{"query":"maria"}}}'
```
## Estrutura
```
src/
index.ts bootstrap HTTP, healthcheck, rotas MCP
auth.ts bearer token opcional
server.ts McpServer e registro das tools
db/
types.ts modelo de domínio
load.ts carga dos JSON e índices em memória
search.ts normalização e pontuação de nomes
tools/ uma tool por arquivo + helpers de resposta
scripts/
seed-utils.ts PRNG, séries de vitais, painéis laboratoriais
cases/ casos clínicos curados
generate-seed.ts valida e escreve data/*.json
data/ a base gerada (versionada)
```
## Scripts
| Comando | Ação |
|---|---|
| `npm run seed` | Regenera `data/` a partir de `scripts/cases/`. |
| `npm run dev` | Servidor em modo watch. |
| `npm run build` | Compila para `dist/`. |
| `npm start` | Roda o build. |
| `npm run typecheck` | Checagem de tipos sem emitir. |
## Limitações conhecidas
- **Somente leitura.** Nenhuma tool grava; não há simulação de prescrição ou evolução criada pelo agente.
- **Sem testes automatizados** nesta versão, por decisão de escopo. O gerador valida integridade referencial, e os tipos cobrem o contrato das tools.
- **Datas ancoradas** em `REF` (`scripts/seed-utils.ts`). Com o passar dos meses os casos "de agora" envelhecem; ajuste `REF` e rode `npm run seed` para atualizá-los.
- **Campos aproximados.** A modelagem é plausível clinicamente, mas não replica o esquema real do TASI. Quando o contrato real aparecer, o ponto de ajuste é `src/db/types.ts` mais as tools.
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues