Skip to main content
Glama
protocolos-pcdt-mcp

protocolos-pcdt-mcp

by fabianofilho

protocolos-pcdt-mcp

Servidor MCP local (stdio) que consulta os PCDTs (Protocolos Clínicos e Diretrizes Terapêuticas) do Ministério da Saúde/Conitec e resume a conduta recomendada para um contexto clínico específico, usando um LLM local. Toda resposta traz o link do PDF oficial.

Por padrão o servidor roda por stdio, na sua máquina, sem porta de rede. Há também um modo connector (HTTP), em que ele só consulta: não chama o LLM e, portanto, não gera resumos.

⚠️ Não substitui o protocolo nem o julgamento clínico

  • Não é fonte oficial. O projeto lê o que a Conitec publica e guarda uma cópia local, que pode estar defasada em relação ao portal.

  • O resumo é gerado por LLM e é uma ajuda de leitura. Protocolos têm exceções, populações específicas e notas de rodapé que um resumo de seis linhas não carrega.

  • A citação literal é conferida, mas a interpretação não. Ver Limitações conhecidas: há um exemplo real de citação correta com conclusão clínica errada.

  • Sem validação clínica. Não foi avaliado por nenhum órgão e não é dispositivo médico.

  • Para conduta, leia o protocolo completo. O link vem em toda resposta.

Requisitos

O quê

Versão

Para quê

Python

3.12+

runtime

uv

recente

dependências e venv

Um LLM local com API OpenAI-compatible

-

resumo direcionado

Espaço em disco

~1 GB

base DuckDB + PDFs cacheados (protocolos são grandes)

Related MCP server: Medical MCP Server

Instalação

git clone https://github.com/fabianofilho/protocolos-pcdt-mcp.git
cd protocolos-pcdt-mcp
uv sync
cp .env.example .env

Configuração

Variável

Padrão

Observação

QWEN_ENDPOINT

http://127.0.0.1:8080/v1

llama.cpp. Ollama: :11434/v1. LM Studio: :1234/v1

QWEN_MODEL

local-model

llama.cpp e LM Studio aceitam qualquer nome

DUCKDB_PATH

./data/pcdt.duckdb

base local

COLETA_DELAY_SEGUNDOS

1

intervalo entre downloads de PDF

uv run pcdt-cli llm                 # confirma o LLM local
uv run pcdt-cli sync --max-pdfs 5   # teste rápido, grava direto na base
uv run pcdt-cli sync --publicar     # coleta (20 PDFs por execução) e troca a base no fim
uv run pcdt-cli consultar asma
uv run pcdt-cli resumir "asma" "paciente gestante"

A coleta baixa no máximo --max-pdfs protocolos por execução: são ~130 PDFs grandes, e a ideia é a base completar ao longo de algumas noites em vez de sobrecarregar o portal numa única. O texto já coletado é preservado entre execuções.

Sync diário com systemd

O servidor MCP não agenda nada sozinho. O caminho oficial para manter a base atualizada é o timer systemd de usuário versionado em deploy/systemd/: ele roda pcdt-cli sync --publicar todo dia às 02:40, com até 30 min de atraso aleatório, e recupera o disparo perdido se a máquina estava desligada.

As units supõem o clone em ~/protocolos-pcdt-mcp e o uv em ~/.local/bin/uv. Se os seus caminhos forem outros, edite WorkingDirectory e ExecStart antes de copiar.

mkdir -p ~/.config/systemd/user
cp deploy/systemd/protocolos-pcdt-sync.service deploy/systemd/protocolos-pcdt-sync.timer \
  ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now protocolos-pcdt-sync.timer
systemctl --user list-timers protocolos-pcdt-sync.timer   # próximo disparo
journalctl --user -u protocolos-pcdt-sync.service         # saída dos syncs

Para o timer rodar sem sessão aberta, habilite loginctl enable-linger $USER.

Ligando ao Claude Code

claude mcp add protocolos-pcdt --scope user \
  -e DUCKDB_PATH=/caminho/para/protocolos-pcdt-mcp/data/pcdt.duckdb \
  -e QWEN_ENDPOINT=http://127.0.0.1:8080/v1 \
  -e QWEN_MODEL=local-model \
  -- uv --directory /caminho/para/protocolos-pcdt-mcp run protocolos-pcdt-mcp

Uso

consultar_protocolo(doenca_ou_condicao: str, limite=10)

limite vai de 1 a 100 e vale para a busca pelo nome; acima disso a chamada é recusada.

Busca primeiro no nome dos PCDTs vigentes:

  • sem acento e sem caixa, com as palavras em qualquer ordem, sempre no começo de uma palavra ("asma" não casa com "citoplasma");

  • trocando algumas siglas pelo nome por extenso (HAS, DPOC, DM1, DM2, TDAH, LES, ELA, IC, DRC, entre outras em nomes.py);

  • aceitando "diabetes"/"diabete" e "mellitus"/"melito", porque a Conitec escreve "Diabete Melito Tipo 1".

Devolve até 10 protocolos pelo nome, com origem: "nome". Se o nome não casar, procura o termo no texto completo e devolve até 5 com origem: "texto" e um aviso: esses só citam o termo e muitas vezes não são o PCDT da condição (por exemplo, "depressão" traz Alzheimer e Parkinson, que mencionam depressão).

O identificador é o nome da condição em minúsculas. Quando o portal pendura uma nota de revisão no nome, como "Asma (anexo alterado em 04/09/2026)", ela vai para nota_atualizacao e fica fora do identificador. Saída real, da base local em 24/09/2026:

{
  "termo": "acidentes ofídicos",
  "total": 1,
  "resultados": [
    {
      "identificador": "acidentes ofídicos",
      "condicao": "Acidentes Ofídicos",
      "status": "Conitec",
      "portaria": "Portaria SECTICS/MS nº 83 - 07/10/2025 (Publicada em 09/10/2025 )",
      "data_portaria": "2025-10-07",
      "nota_atualizacao": null,
      "url_pdf": "https://www.gov.br/conitec/pt-br/midias/protocolos/pcdt_acidentes_ofidicos_final.pdf/@@display-file/file",
      "url_resumido": "https://www.gov.br/conitec/pt-br/midias/protocolos/2026/pcdt-resumido/pcdt-resumido-acidentes-ofidicos/@@display-file/file",
      "vigente": true,
      "secoes_disponiveis": ["introducao", "diagnostico", "classificacao", "monitoramento", "tratamento"],
      "texto_completo_disponivel": true,
      "extracao_incompleta": false,
      "origem": "nome"
    }
  ],
  "aviso": null
}

status vem do CSV de dados abertos do Ministério da Saúde e é parcial: em 24/09/2026, só 43 dos 132 PCDTs da listagem tinham status, porque boa parte dos nomes do CSV não existe na tabela do portal. status: null não quer dizer que o protocolo não está aprovado; a listagem da Conitec só traz PCDTs vigentes.

resumir_conduta(pcdt_id: str, contexto_clinico: str)

Extrai do protocolo a parte que responde ao contexto, em vez de devolver o documento inteiro. Aceita o identificador de consultar_protocolo ou o nome da condição sem a nota de revisão (por exemplo, asma). Precisa do LLM local e do texto completo já coletado; sem um dos dois, devolve só o link do PDF e um aviso dizendo o que faltou. No modo connector o LLM nunca é chamado e a resposta vem sempre com resumo: null (ver abaixo).

{
  "condicao": "Acidentes Ofídicos",
  "resumo": {
    "resumo": "O paciente deve receber soroterapia antiveneno específica para o tipo de envenenamento.",
    "secao_origem": "1.3. Acesso a soroterapia antiveneno",
    "citacao_literal": "é necessário utilizar a soroterapia antiveneno específica, correspondente ao tipo de envenenamento",
    "citacao_confere": true,
    "fracao_citacao_verificada": 1.0
  },
  "url_pdf": "https://www.gov.br/conitec/..."
}

A citação é conferida, não só pedida

O prompt exige a citação literal do trecho que sustenta o resumo. Um modelo pequeno às vezes "cita" parafraseando, ou, pior, costura frases reais de partes diferentes do documento num único bloco de aspas. Então o código confere:

Campo

O que significa

citacao_confere

a citação existe inteira e contígua no protocolo

fracao_citacao_verificada

quanto dela existe, frase a frase (0 a 1)

Fração alta com citacao_confere: false é o caso mais traiçoeiro: parece legítimo na leitura e não é. Marcadores de omissão ("[...]", "(...)", reticências) nas pontas da citação são ignorados; no meio, significam que algo foi pulado, e a citação deixa de contar como contígua. Isso aconteceu no primeiro teste real deste projeto, com o PCDT de Acidentes Ofídicos, e é por isso que os dois campos existem.

Modo connector (servidor HTTP)

Por padrão o servidor fala stdio: o cliente sobe o processo na máquina de quem usa. Com TRANSPORTE=streamable-http, ele vira um servidor alcançável pela rede, que é o que o Claude aceita como custom connector.

TRANSPORTE=streamable-http HTTP_PORTA=8000 uv run protocolos-pcdt-mcp

Variável

Padrão

Observação

TRANSPORTE

stdio

streamable-http liga o modo connector

HTTP_HOST / HTTP_PORTA / HTTP_PATH

127.0.0.1 / 8000 / /mcp

HTTP_STATELESS

true

cada requisição independente; escala melhor

HTTP_LIMITE_GLOBAL_POR_MINUTO

1200

o teto que protege a máquina

HTTP_LIMITE_POR_MINUTO

600

por origem, contra chamada direta

HTTP_HOSTS_PUBLICOS

vazio

nomes públicos aceitos no Host, separados por vírgula

Por que dois limites. Quando o Claude chama um connector remoto, as requisições chegam dos IPs da Anthropic, não do usuário final. Limitar só por IP colocaria todos os usuários no mesmo balde: ou derruba todo mundo junto, ou não protege nada. O teto global é o que vale para esse tráfego; o por origem serve contra quem chama o servidor direto. X-Forwarded-For só é considerado quando a conexão vem do loopback (o proxy local), e vale a entrada mais à direita, a que o proxy acrescentou.

No modo connector, resumir_conduta não resume

O servidor HTTP não chama o LLM. Resumir sob demanda faria quem hospeda pagar inferência para qualquer pessoa que chame o connector. Não existe cache de resumos neste projeto (cada resumo depende do contexto clínico perguntado), então, nesse modo, a tool devolve o identificador, a condição, o link do PDF, resumo: null e um aviso dizendo que nenhum resumo foi gerado. Nada é inventado no lugar. consultar_protocolo funciona igual nos dois modos.

Atrás de um proxy ou túnel, declare o nome público

O SDK do MCP valida o cabeçalho Host e responde 421 Invalid Host ao que não reconhece. É proteção contra DNS rebinding, um ataque em que um site qualquer faz o navegador da vítima conversar com um servidor que só deveria ser local. Atrás de um túnel, o Host que chega é o nome público, então ele precisa ser declarado:

HTTP_HOSTS_PUBLICOS=mcp.exemplo.ts.net

O loopback continua valendo junto, porque é assim que se testa o servidor de dentro da máquina.

Rodar como serviço

deploy/systemd/protocolos-pcdt-connector.service é a unit de usuário do servidor HTTP. Ela escuta só em 127.0.0.1:8104; expor para fora é uma camada à parte (proxy reverso com TLS ou túnel) que aponta para essa porta. Ajuste HTTP_HOSTS_PUBLICOS para o seu nome público antes de habilitar.

cp deploy/systemd/protocolos-pcdt-connector.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now protocolos-pcdt-connector.service

O sync não interrompe quem está lendo

pcdt-cli sync --publicar constrói a base nova ao lado e troca as duas por rename no fim. O servidor abre a base somente para leitura a cada requisição; quem já abriu continua no arquivo antigo até fechar, e quem abrir depois pega o novo. É o modo que o timer usa, e por dois motivos.

O primeiro é a concorrência: o DuckDB não aceita leitor e escritor de processos diferentes ao mesmo tempo. Sem a troca, as consultas durante a coleta (até uma hora, com os downloads de PDF) recebiam o aviso de base travada em vez de resultado, e um sync que começasse no instante de uma consulta falhava.

O segundo é o tamanho. O DuckDB não devolve o espaço das páginas antigas, e a coleta reescreve o texto completo dos protocolos todo dia: em 11/10/2026, depois de semanas de syncs diários, o arquivo estava em 1.062 MB para 132 protocolos, contra 49 MB compactado. A base nova é clonada da servida com COPY FROM DATABASE, e a cópia já sai compactada.

Sem --publicar, o sync grava direto em DUCKDB_PATH, que é o que se quer num teste rápido com --max-pdfs. A troca recusa publicar uma base que encolheu mais de 10% em número de protocolos, para coleta interrompida não substituir a base boa em silêncio; quando a queda é real, --forcar publica.

Limitações conhecidas

Conferir a citação não pega erro de interpretação. Num teste com "paciente picado por cascavel", o modelo local devolveu uma citação real e contígua do protocolo e mesmo assim classificou o caso como envenenamento botrópico, quando cascavel é crotálico. A citação estava certa; o raciocínio em cima dela, errado. Esta é a limitação mais importante do projeto.

O protocolo é truncado antes de ir para o modelo. Protocolos passam de 200 mil caracteres; o recorte prioriza a vizinhança das palavras do contexto perguntado, mas pode cortar fora a parte relevante.

A segmentação por seções é heurística. A estrutura dos PCDTs varia entre protocolos antigos e novos. Quando os títulos não são reconhecíveis, secoes_disponiveis vem vazio e só o texto corrido fica disponível, de propósito, para não inventar estrutura.

A base começa quase vazia. Por causa do teto de PDFs por execução, os primeiros syncs trazem a listagem completa mas pouco texto. texto_completo_disponivel diz quais já têm. Na instância do mantenedor, em 24/09/2026, eram 64 de 132 com texto; no ritmo de 20 PDFs por noite, a base deve completar em cerca de 4 syncs (estimativa). Até lá, PCDTs como hipertensão arterial sistêmica e os de HIV aparecem na consulta, mas resumir_conduta responde que o texto ainda não foi coletado. pcdt-cli schema mostra a contagem.

Quando o PCDT muda, o texto antigo é descartado. Se a URL do PDF, a data da portaria ou a nota de revisão mudarem, o sync reextrai o texto, com prioridade na cota da noite. Se não couber na cota, ou se o download falhar, o protocolo fica sem texto até um sync conseguir baixar o PDF novo, em vez de resumir a versão velha com o link da nova. Quando só a nota ou a portaria mudam e a URL continua a mesma, o PDF velho sai do cache em disco na hora, para não ser reextraído depois.

O status é parcial. Ver a seção de consultar_protocolo.

A busca no texto é um último recurso. Ela acha protocolos que citam o termo, não o PCDT da condição. Siglas fora da lista de nomes.py também caem nela.

Listagem parcial não despromove ninguém. Se a listagem do portal trouxer menos de 80% dos PCDTs vigentes na base, o sync não tira ninguém de vigente e deixa um aviso no journal.

PDFs digitalizados não têm camada de texto. Nesses casos o registro fica com extracao_incompleta: true e só os metadados.

As URLs das fontes podem mudar. Estão em coleta/listagem.py, confirmadas em 20/09/2026. Observação prática: os links .csv que o portal de dados abertos exibe estão desatualizados e devolvem 403; os que funcionam terminam em .zip.

Privacidade

  • Sai da máquina: requisições ao gov.br/conitec e ao bucket de dados abertos do Ministério da Saúde, para a listagem e os PDFs públicos.

  • Não sai: a condição e o contexto clínico que você consulta ficam entre a base local e o seu LLM local.

  • Sem telemetria, sem analytics.

  • No modo connector, o que você consulta passa pela máquina que hospeda o servidor (e pelo túnel ou proxy na frente dela). O contexto clínico não vai para LLM nenhum, porque nesse modo o LLM não é chamado.

Atenção: o contexto_clinico que você digita vai para o seu LLM. Se ele estiver hospedado fora da sua máquina, o texto vai junto, este projeto não impede isso, ao contrário do revisor-notas-mcp.

Atualizando de uma base anterior

A versão 0.1.0 tira a nota de revisão do identificador e cria a coluna nota_atualizacao. A migração roda no próximo pcdt-cli sync (ou em qualquer abertura da base para escrita, como pcdt-cli schema), preserva o texto já extraído e refaz o índice FTS. Até lá, o servidor continua lendo a base antiga: os identificadores aparecem com a nota, e resumir_conduta aceita o nome sem ela.

Segurança

Ver SECURITY.md. Mudanças por versão em CHANGELOG.md.

Contribuindo

Veja CONTRIBUTING.md. Não rode a coleta em loop contra o portal.

Licença e atribuição

Apache License 2.0: escolhida por o projeto tocar em conduta clínica.

Construído no contexto do IA.med.

Available Tools

2 tools
consultar_protocoloA

Consulta o PCDT vigente do Ministério da Saúde para uma doença ou condição.

Procura primeiro no nome dos PCDTs, sem acento, com as palavras em qualquer ordem e aceitando algumas siglas ("HAS", "DPOC", "DM2") e a grafia "diabetes mellitus". Se o nome não casar, procura o termo no texto dos protocolos e devolve até 5, com origem="texto" e um aviso: esses só citam o termo e muitas vezes não são o PCDT da condição.

Cada resultado traz identificador (use em resumir_conduta), nome da condição, portaria, link do PDF completo e do PCDT resumido, seções extraídas e se o texto completo já foi coletado. O status vem do CSV de dados abertos e só existe para parte dos PCDTs; null não quer dizer que o protocolo não está aprovado. Se houver mais de um protocolo pelo nome, devolve todos (até 10), a escolha é de quem pergunta.

Args: doenca_ou_condicao: nome da doença ou condição, por exemplo "asma".

Inputs: doenca_ou_condicao (required). Returns: aviso, termo, total, resultados

resumir_condutaA

Resume a conduta de um PCDT para um contexto clínico específico.

Em vez de devolver o protocolo inteiro, extrai a parte que responde ao contexto, por exemplo "paciente com contraindicação a metformina". A resposta traz sempre a citação literal do trecho que sustenta o resumo, e diz se essa citação foi de fato encontrada no protocolo.

É ajuda de leitura, não substitui o protocolo: o link do PDF vem junto.

Precisa de um LLM local configurado e do texto completo do protocolo já coletado; sem isso, devolve só o link do PDF e um aviso.

Args: pcdt_id: identificador devolvido por consultar_protocolo. O nome da condição sem a nota de revisão também é aceito, por exemplo "asma". contexto_clinico: a situação concreta sobre a qual se quer a conduta.

Inputs: pcdt_id (required), contexto_clinico (required). Returns: aviso, resumo, url_pdf, condicao, +2 more

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4.7/5.0

    Scored across 2 tools

    Disambiguation5/5

    The two tools have completely distinct purposes: one searches for PCDT documents, the other summarizes a specific clinical conduct from a selected PCDT. There is no overlap or ambiguity in their roles.

    Naming Consistency5/5

    Both tool names follow the same verb_noun pattern in Portuguese: 'consultar_protocolo' (consult protocol) and 'resumir_conduta' (summarize conduct). The naming is consistent and predictable.

    Tool Count4/5

    With only 2 tools, the server is slightly under the typical well-scoped range, but the narrow domain (PCDT consultation and summarization) justifies this minimal set. The tools cover the core workflow without unnecessary extras.

    Completeness4/5

    The server covers the essential operations: finding a protocol and summarizing a specific conduct. A minor gap exists in that there is no explicit tool to fetch the full protocol text, but the first tool provides links and sections, and the second tool gracefully degrades if full text is unavailable.

    Maintenance

    ActivityMaintained
    ResponsivenessOutdated data

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      A
      maintenance
      Enables AI assistants to query authoritative medical sources such as FDA, WHO, PubMed, RxNorm, and ClinicalTrials.gov for drug information, safety data, health statistics, clinical guidelines, pediatric resources, and evidence-graded literature. Runs locally with no API keys or data leaving the machine.
      83 npm
      ISC
    • A
      license
      Not graded
      quality
      A
      maintenance
      Enables AI assistants to query authoritative medical sources such as FDA, WHO, PubMed, RxNorm, and ClinicalTrials.gov for drug information, safety data, health statistics, medical literature, clinical guidelines, and pediatric resources. Runs fully locally with no API keys or data leaving the machine, and includes resilience features like rate limiting, circuit breakers, caching, and health checks.
      83 npm
      ISC
    • A
      license
      Not graded
      quality
      A
      maintenance
      Enables AI assistants to query authoritative sources like FDA, WHO, PubMed, RxNorm, and Google Scholar for drug information, health statistics, clinical guidelines, trials, and medical literature. It runs locally with no API keys, so data stays private while results come back with evidence grading and resilient fallbacks.
      83 npm
      ISC