Projeto IA.med Licença Apache-2.0 Nada sai da máquina 52 testes (lido em 12/10/2026)

revisor-notas-mcp

Servidor MCP que confere notas clínicas no formato SOAP: regras determinísticas mais uma checagem de coerência por LLM local. O texto da nota não sai do seu computador.

Não substitui o julgamento clínico

  • Não é software de apoio à decisão clínica: não foi validado clinicamente, não passou por órgão regulador e não é dispositivo médico.
  • A checagem de coerência depende do modelo local: com ele fora do ar, só as regras determinísticas rodam. Além disso, ela é probabilística, feita por um LLM lendo texto livre. Erra dos dois lados: ausência de aviso não atesta que a nota está correta, e um aviso pode ser falso positivo.
  • A responsabilidade pela nota continua de quem assina. A ferramenta aponta o que pode ter faltado; não sabe do paciente nada além do que está escrito.
  • As regras foram escritas para um template específico de telemedicina. Para outro formato, é preciso ajustá-las.

O problema

Por que este projeto existe

Formalmente completa, clinicamente incoerente

Uma nota pode ter todos os campos preenchidos e mesmo assim ter uma conduta que não fecha com a queixa. Regra de preenchimento não pega esse tipo de erro.

Revisar à mão é caro e inconstante

Conferir campo a campo, mais coerência clínica e sinais de alarme esperados, em cada nota, cansa e falha. É o tipo de checagem que uma máquina faz sem se distrair.

Nota clínica não pode sair da máquina

Mandar o texto de uma nota para um serviço externo é risco de privacidade. Por isso a checagem tem que rodar localmente, sem gravar nada.

Como funciona

Duas camadas sobre a mesma nota

  1. 01

    Parser SOAP

    Lê a nota no formato F/S/O/A/P, tolerando variação de formatação (marcador, caixa, espaço).

  2. 02

    Regras determinísticas

    Rodam sempre, sem LLM: cabeçalho, as cinco seções, CID em formato válido, itens do plano, sinais de alarme, rodapé e os campos do subjetivo.

  3. 03

    Checagem semântica

    Um LLM local aponta incoerência entre queixa, exame e conduta, e sinais de alarme do diagnóstico que não aparecem investigados.

  4. 04

    Problemas com origem

    Cada achado sai com seção, severidade, sugestão e a origem: regra ou semantica.

Privacidade em código, não só em texto: o cliente do LLM recusa com exceção qualquer endpoint que não seja localhost, e nada é gravado (sem banco, sem cache, sem log do conteúdo). Se a checagem semântica não rodar (LLM fora do ar, falha, nota vazia), as regras respondem sozinhas e as duas tools dizem o motivo real em motivo_semantica_pulada e no aviso. Nota vazia devolve um único erro, Nota vazia.

As ferramentas

Duas tools MCP expostas ao modelo

Cada problema traz a seção, a severidade, a sugestão e a origem (regra ou semantica). Os exemplos abaixo são a saída real das duas tools para a mesma nota sintética: sem erros de formato, mas com um alerta clínico que só a camada semântica pega.

validar_nota_soap

Lista os problemas encontrados, cada um com seção, severidade, sugestão e a origem.

validar_nota_soap(texto_nota: str) -> {
  total_erros, total_avisos,
  problemas: [{secao, severidade, descricao, sugestao, origem}],
  checagem_semantica_feita, motivo_semantica_pulada, aviso
}
{
  "total_erros": 0,
  "total_avisos": 2,
  "problemas": [
    {
      "secao": "S, A, P",
      "severidade": "aviso",
      "descricao": "A queixa principal é dor torácica em aperto irradiando para o braço esquerdo, altamente sugestiva de síndrome coronariana aguda, mas a hipótese diagnóstica final é dor muscular (CID M79.1) e a conduta é analgésico comum, sem investigação ou tratamento para causa cardíaca.",
      "sugestao": "Confira se a conduta corresponde à hipótese e ao que foi relatado.",
      "origem": "semantica"
    },
    {
      "secao": "S",
      "severidade": "aviso",
      "descricao": "Sinais de alarme esperados para a hipótese que não aparecem como investigados: sudorese, dispneia, palidez.",
      "sugestao": "Registre a investigação, mesmo que negativa ('nega ...').",
      "origem": "semantica"
    }
  ],
  "checagem_semantica_feita": true,
  "motivo_semantica_pulada": null,
  "aviso": "As checagens de coerência e de sinais de alarme são feitas por um LLM local e são probabilísticas: leia como sugestão de revisão, não como veredito clínico."
}
Saída real de uma nota sintética (dor torácica), com LLM local

sugerir_correcoes

A mesma nota com os problemas inseridos como linhas <<ERRO: ...>> ou <<AVISO: ...>>. Não reescreve o texto original: nenhuma linha da nota muda, e quem decide o que mudar é quem assina.

sugerir_correcoes(texto_nota: str) -> {
  nota_anotada, total_anotacoes,
  checagem_semantica_feita, motivo_semantica_pulada, aviso
}
  • Cada problema aparece uma única vez, no fim da primeira seção que ele cita e que existe na nota, com a sugestão depois de → quando houver.
  • Achado que cita várias seções leva a lista entre colchetes, como <<AVISO [S, A, P]: ...>>.
  • O que não tem seção na nota (cabeçalho ausente, seção ausente) vai para o bloco <<PROBLEMAS NO DOCUMENTO>> no topo.
  • total_anotacoes é igual ao número de problemas.
#TELEMEDICINA#
-F: Paciente refere dor no peito.
-S: Refere dor torácica em aperto há 3 horas, irradiando para o braço esquerdo.
...
Hábitos: tabagista, 20 maços-ano.
    <<AVISO [S, A, P]: A queixa principal é dor torácica em aperto irradiando para o braço esquerdo, altamente sugestiva de síndrome coronariana aguda, mas a hipótese diagnóstica final é dor muscular (CID M79.1) e a conduta é analgésico simples, sem investigação ou tratamento para causa cardíaca. → Confira se a conduta corresponde à hipótese e ao que foi relatado.>>
    <<AVISO: Sinais de alarme esperados para a hipótese que não aparecem como investigados: sudorese, dispneia. → Registre a investigação, mesmo que negativa ('nega ...').>>
-O: Ectoscopia em tela: BEG, consciente, orientado em tempo e espaço.
-A: Dor muscular (CID: M79.1).
-P:
...
Trecho real de nota_anotada para a mesma nota sintética
<<PROBLEMAS NO DOCUMENTO>>
    <<ERRO [O]: Seção -O: ausente ou vazia. → Preencha a seção -O: conforme o template.>>
#TELEMEDICINA#
...
Seção ausente vai para o topo, com o rótulo [O] (saída real sem LLM, nota sintética de teste)

Como rodar

Roda na sua máquina

Não é um serviço hospedado e não tem banco de dados. A distribuição é por clone + uv (não há pacote no PyPI): você clona, aponta para o seu LLM local (obrigatoriamente localhost) e o servidor MCP roda ali, via stdio. Precisa de Python 3.12+ e usa o SDK MCP 2.x (mcp>=2.2,<3). Sem LLM, a validação por regras responde sozinha.

# 1. clonar e instalar
git clone https://github.com/fabianofilho/revisor-notas-mcp.git
cd revisor-notas-mcp
uv sync
cp .env.example .env

# 2. o endpoint do LLM PRECISA ser localhost (outro host é recusado)
uv run revisor-cli llm                        # confirma o LLM local

# 3. validar uma nota
uv run revisor-cli validar nota.txt           # ou cole a nota no stdin
uv run revisor-cli validar nota.txt --sem-llm # só as regras determinísticas
uv run revisor-cli anotar nota.txt            # nota com anotações inline

# 4. registrar o servidor MCP (stdio) no seu cliente, ex.: Claude Code
claude mcp add revisor-notas --scope user \
  -e QWEN_ENDPOINT=http://127.0.0.1:8080/v1 \
  -e QWEN_MODEL=local-model \
  -- uv --directory /caminho/para/revisor-notas-mcp run revisor-notas-mcp
Confira variáveis e opções no README do projeto.

O que sai da sua máquina: nada. O texto da nota vai só para o seu LLM local, pelo localhost. Um endpoint apontando para fora estoura com exceção em vez de degradar em silêncio, e há teste conferindo que nenhum trecho da nota aparece em log, mesmo quando a chamada ao LLM falha.