simlock
병렬 코딩 에이전트가 동일 시뮬레이터를 놓고 충돌하지 않도록 제어 평면 제공
보건부 최신 PCDT를 조회하고 임상 상황에 맞게 요약·LLM 로컬 적용 및 원문 인용을 제공하는 오픈소스 MCP 서버
Servidor MCP open source que consulta os PCDTs vigentes do Ministério da Saúde/Conitec e resume a conduta para um contexto clínico, com LLM local e citação literal conferida. Um projeto IA.med.
개발 도구Python · JinjaGitHub @fabianofilho온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기Servidor MCP local (stdio) que consulta os PCDTs vigentes da Conitec e resume a conduta com citação literal conferida
저장소 README에서 가져옴
README 전체를 불러오는 중입니다.
README 전문 보기 ↗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.
| 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) |
git clone https://github.com/fabianofilho/protocolos-pcdt-mcp.git
cd protocolos-pcdt-mcp
uv sync
cp .env.example .env
| 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.
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.
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
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:
HAS, DPOC, DM1, DM2, TDAH,
LES, ELA, IC, DRC, entre outras em nomes.py);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/..."
}
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.
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.
resumir_conduta não resumeO 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.
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.
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
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.
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.
gov.br/conitec e ao bucket de dados abertos do
Ministério da Saúde, para a listagem e os PDFs públicos.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.
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.
Ver SECURITY.md. Mudanças por versão em CHANGELOG.md.
Veja CONTRIBUTING.md. Não rode a coleta em loop contra o portal.
Apache License 2.0: escolhida por o projeto tocar em conduta clínica.
Construído no contexto do IA.med.