anvisa-mcp
Query Anvisa open data for drug registration status and recent Class III/IV medical devices with heuristic AI-use classifications.
Search medication registrations by commercial name, active ingredient, or registration number (with/without punctuation), returning status, registration number, expiry, holder, and category.
Control medication result size (1–200) and see total matches/truncation via
totalandtruncado.List recent Class III/IV medical devices within a 1–3650 day window, optionally filtering to software-suggesting records and/or those classified as using AI.
Read per-device heuristic AI/ML verdicts with confidence, justification, origin (
llm,cache,indisponivel,nao_classificado), and evidence-check status.Run local stdio mode with an OpenAI-compatible LLM to classify uncached devices on demand, without persisting the verdict.
Run connector/HTTP mode read-only, serving only cached classifications and never calling the LLM on demand.
Check data freshness/provenance:
coletado_em, stale-data warnings after 48h,visto_na_ultima_coleta, andfontemock only when the local DB is unavailable.Use the public connector instance subject to rate limits (600/min per origin, 1200/min total) and parameter caps.
Treat results as non-official: medication status comes from Anvisa open files, and AI-use classification is heuristic, not regulatory/clinical validation.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@anvisa-mcpWhat's the registration status of dipirona?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
anvisa-mcp
Servidor MCP que expõe duas consultas à base de dados abertos da Anvisa a um LLM: status de registro de medicamentos e busca de dispositivos médicos recentes, com uma tentativa de classificar quais deles usam IA. Roda local, contra um LLM que você mesmo hospeda, ou pelo connector público descrito abaixo.
⚠️ Não é fonte oficial
Não substitui a consulta ao portal da Anvisa. Os dados vêm dos arquivos abertos publicados pela agência e podem estar defasados em relação ao sistema oficial.
A classificação de uso de IA é heurística, feita por um LLM lendo texto livre. A Anvisa não publica campo estruturado sobre isso. Cada resposta traz confiança e justificativa justamente porque o veredito pode estar errado.
Não houve validação clínica ou regulatória deste software. Ele não foi avaliado por nenhum órgão e não é um dispositivo médico.
Para qualquer decisão regulatória, use o registro oficial no portal da Anvisa.
Connector público
Há uma instância rodando este código, aberta, que dá para adicionar ao Claude como custom connector sem instalar nada:
https://debian-f.tailf42a96.ts.net/anvisa/mcpCondições de uso:
Dados públicos da Anvisa, sem garantia. É a mesma base que o sync abaixo monta a partir dos arquivos abertos da agência, atualizada uma vez por dia de madrugada. Cada resposta traz
coletado_em, a hora da última coleta, e avisa quando ela passou de 48 horas. Não substitui o portal da Anvisa (ver o aviso acima).Sem garantia de disponibilidade. Roda numa máquina pessoal atrás do Tailscale Funnel. Pode sair do ar, reiniciar ou mudar de endereço sem aviso.
Limites de taxa: 600 requisições por minuto por origem e 1.200 por minuto no total. Acima disso a resposta é HTTP 429 por um minuto.
Teto dos parâmetros:
limitede 1 a 200 nas duas tools ediasde 1 a 3650. Valores fora da faixa são recusados.Somente leitura e sem LLM na hora: o connector não classifica sob demanda. Ele serve os vereditos que a coleta diária gravou, que cobrem os dispositivos dos últimos 10 anos que passam no filtro de software. O resto vem como
nao_classificado.Os termos consultados chegam ao servidor, diferente do uso local. O código não registra os termos nem mantém log de acesso (o uvicorn roda em nível
warning); o que vai para o log é a origem da requisição quando ela passa do limite de taxa.
Related MCP server: OpenFDA FastMCP Server
Requisitos
O quê | Versão | Para quê |
Python | 3.12+ | runtime |
recente | dependências e venv | |
Um LLM local com API OpenAI-compatible | - | classificar dispositivos (llama.cpp, Ollama, LM Studio) |
Espaço em disco | ~500 MB | base DuckDB (~42 MB) + dependências |
Não é preciso servidor de banco: o DuckDB é um arquivo.
Instalação
git clone https://github.com/fabianofilho/anvisa-mcp.git
cd anvisa-mcp
uv sync
cp .env.example .envConfiguração
Edite o .env com o endereço do seu LLM:
Variável | Padrão | Observação |
|
| llama.cpp. Ollama: |
|
| llama.cpp e LM Studio aceitam qualquer nome; o Ollama exige o exato |
|
| onde a base local fica |
|
| timeout por chamada ao LLM |
Confirme que o LLM responde e baixe os dados:
uv run anvisa-cli llm # deve imprimir a resposta do modelo
uv run anvisa-cli sync # ~1 min: baixa os dois CSVs da Anvisa
uv run anvisa-cli schema # contagens da baseO sync é idempotente: rode de novo quando quiser atualizar. A Anvisa republica os
arquivos diariamente (D-1). O código não agenda nada sozinho: para rodar todo dia, use o
timer systemd de deploy/ (ver Rodar como serviço) ou o agendador
que preferir chamando anvisa-cli sync.
Para classificar os dispositivos e gravar os vereditos na base:
uv run anvisa-cli classificar --dias 365 # só o que ainda não tem vereditoLigando ao Claude Code
Troque /caminho/para/anvisa-mcp pelo caminho real do clone. Os caminhos precisam ser
absolutos: o servidor é lançado de qualquer diretório, então ./data/anvisa.duckdb
relativo não resolveria.
claude mcp add anvisa --scope user \
-e DUCKDB_PATH=/caminho/para/anvisa-mcp/data/anvisa.duckdb \
-e QWEN_ENDPOINT=http://127.0.0.1:8080/v1 \
-e QWEN_MODEL=local-model \
-- uv --directory /caminho/para/anvisa-mcp run anvisa-mcpUso
consultar_status_medicamento(termo: str, limite: int = 20)
Busca por trecho do nome comercial ou do princípio ativo, ignorando acento e caixa, ou
pelo número de registro completo, com ou sem pontos (1.1819.0006 e 118190006 dão o
mesmo resultado; o número de 13 dígitos da apresentação também serve). Devolve todos os
registros que casam: grafias variam e a ambiguidade é de quem pergunta. Registros ativos
vêm primeiro, porque dois terços da base são inativos.
limite vai de 1 a 200. total é quantos casam na base inteira, e truncado=true diz
que há mais do que veio: "dipirona" casa com 557 registros, um por detentor e
apresentação. situacao vem como a Anvisa publica, Ativo ou Inativo, e
data_situacao é a data de vencimento do registro.
Saída real, de 24/09/2026:
{
"termo_consultado": "1.1819.0006",
"fonte": "duckdb",
"coletado_em": "2026-09-24T03:22:04.857697",
"total": 1,
"retornados": 1,
"truncado": false,
"resultados": [
{
"numero_registro": "118190006",
"nome_produto": "DELTALAB",
"principio_ativo": "deltametrina",
"empresa_detentora": "92265552000905 - MULTILAB INDUSTRIA E COMERCIO DE PRODUTOS FARMACEUTICOS LTDA",
"situacao": "Ativo",
"data_situacao": "2029-02-01",
"categoria": "Similar",
"visto_na_ultima_coleta": true
}
],
"aviso": null
}Termo sem correspondência devolve resultados: [], total: 0 e um aviso. Dados de
exemplo, com fonte: "mock" e nomes terminados em "(EXEMPLO MOCK)", só aparecem quando a
base não existe, está vazia ou não pôde ser aberta.
buscar_samd_recentes(dias: int = 90, apenas_com_ia: bool = True, apenas_software: bool = True, limite: int = 50)
Dispositivos Classe III/IV registrados na janela, com um veredito heurístico sobre uso de
IA. dias vai de 1 a 3650 e limite de 1 a 200. Trecho de uma saída real, de
24/09/2026, no connector (dias=3650, limite=200):
{
"dias": 3650,
"apenas_com_ia": true,
"fonte": "duckdb",
"coletado_em": "2026-09-24T03:22:40.940760",
"total": 310,
"analisados": 200,
"retornados": 1,
"truncado": true,
"indeterminados": 158,
"resultados": [
{
"numero_registro": "80102513702",
"nome_produto": "CAD4TB",
"classe_risco": "III",
"situacao": "29/06/2036",
"validade_registro": "2036-06-29",
"data_registro": "2026-06-29",
"classificacao": {
"usa_ia": true,
"confianca": 0.6,
"justificativa": "O nome 'CAD4TB' sugere uso de CAD (Computer-Aided Diagnosis) para tuberculose, e o fabricante 'DELFT AI B.V.' indica forte probabilidade de uso de IA, mas a documentação fornecida é insuficiente para confirmar o funcionamento técnico.",
"evidencia_confere": null,
"origem": "cache",
"heuristica": true
}
}
]
}total é o universo da janela depois do filtro de software, analisados é quantos esta
chamada olhou e indeterminados conta os que ficaram de fora por confiança baixa ou por
não terem veredito, que não é o mesmo que "não usa IA". evidencia_confere é false
quando o modelo citou como evidência algo que não está no registro, e null nos
vereditos gravados antes dessa checagem existir.
O servidor MCP nunca grava na base, em nenhum modo. Em stdio, com o LLM no ar, ele
classifica na hora o que não está no cache e avisa que o veredito não ficou gravado; para
gravar, rode anvisa-cli classificar. Assim um cliente stdio apontado para a mesma base de
um connector não disputa o lock de escrita com ele.
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_HOST=127.0.0.1 HTTP_PORTA=8000 uv run anvisa-mcpEle escuta no loopback. Para expor, ponha um proxy reverso com TLS ou um túnel na frente (ver abaixo).
Variável | Padrão | Observação |
|
|
|
|
| |
|
| cada requisição independente; escala melhor |
|
| o teto que protege a máquina |
|
| por origem, contra chamada direta |
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.
No modo connector o servidor não chama o LLM
buscar_samd_recentes normalmente pede a um LLM local que leia o texto do registro e
julgue se o produto usa IA. Num connector isso não se sustenta: cada usuário pagaria a
espera de uma fila de GPU compartilhada, e a classificação grava no DuckDB, que recusa
abrir para escrita enquanto houver leitor.
Então, com TRANSPORTE=streamable-http, o servidor serve só o que já está no cache e
diz isso na resposta. Quem classifica é a coleta, fora do processo do servidor:
uv run anvisa-cli sync --publicar --classificar --dias-classificacao 3650--classificar é o único momento em que o LLM entra num deploy de connector. Sem ele a
coleta atualiza os registros mas não julga os novos, e eles chegam ao servidor como
nao_classificado. A janela padrão de --dias-classificacao é 365 dias; o deploy oficial
usa 3650 para que as consultas de até 10 anos venham classificadas. Fora da janela, o
esperado é nao_classificado.
Cada item traz de onde veio o veredito, em origem_classificacao:
Valor | Significado |
| classificado agora (só acontece em stdio, e não fica gravado) |
| classificado numa coleta anterior |
| connector sem o item no cache: indeterminado, não é "não usa IA" |
| LLM configurado mas fora do ar |
A diferença importa. Um registro que ninguém classificou ainda não é um registro sem IA, e a resposta nunca conta os dois juntos.
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.
Quando o servidor fica atrás de um túnel, o Host que chega é o nome público, não
127.0.0.1, e toda requisição legítima leva 421. A saída certa é declarar o nome, não
desligar a checagem:
HTTP_HOSTS_PUBLICOS=mcp.exemplo.ts.netAceita vários separados por vírgula. O loopback continua valendo junto, porque é assim que se testa o servidor de dentro da máquina.
Rodar como serviço
deploy/ traz as três units de usuário que rodam o connector oficial:
Unit | O que faz |
| servidor HTTP na porta 8101, só loopback, |
|
|
| dispara o sync todo dia às 03:20, com até 30 min de espalhamento |
Elas esperam o repositório em ~/anvisa-mcp e o uv em ~/.local/bin; ajuste os caminhos
se o seu layout for outro. Em anvisa-connector.service, troque HTTP_HOSTS_PUBLICOS pelo
nome público da sua instância. Para instalar:
cp deploy/anvisa-connector.service deploy/anvisa-sync.service deploy/anvisa-sync.timer \
~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now anvisa-connector.service anvisa-sync.timerO connector escuta só em 127.0.0.1. Expor para fora é uma camada à parte, um proxy reverso com TLS ou um túnel, que aponta para essa porta. Manter assim deixa a decisão de expor num lugar só, em vez de espalhada em variável de ambiente. Se o processo morrer, o systemd sobe de novo em 5 segundos.
O connector só lê. Quem escreve é o sync, que roda separado e troca o arquivo por rename.
--reclassificar refaz uma vez os vereditos gravados antes da checagem de evidência
(evidencia_confere nulo), inclusive os que estão fora da janela ou do filtro de software;
depois que todos passaram por ela, a opção não custa nada.
Não há aviso externo quando o sync falha: o connector segue servindo a base anterior, e a
falha aparece em journalctl --user -u anvisa-sync e no coletado_em das respostas, que
ganham aviso quando a coleta passa de 48 horas.
Risco conhecido: o connector roda o código da árvore de trabalho. O ExecStart sobe o
que estiver em ~/anvisa-mcp no momento de cada restart, inclusive o automático. Se essa
pasta também for onde se desenvolve, trocar de branch ali muda o que está em produção no
próximo restart. Para isolar, aponte a unit para um checkout dedicado, por exemplo um git worktree fixo na main, mantendo DUCKDB_PATH na base que o sync publica.
A base não vai junto, e o sync roda fora
O DuckDB recusa abrir para escrita enquanto houver um leitor, e no modo connector o servidor abre a base a cada requisição. Escrever direto no arquivo servido falharia sempre que a coleta caísse em cima de uma consulta.
Por isso o sync usa --publicar: constrói a base ao lado e troca por os.replace, que é
atômico no POSIX. Quem já abriu continua no arquivo antigo até fechar, o tempo de uma
requisição; quem abrir depois pega o novo.
A base ao lado começa como cópia da que está sendo servida, não vazia. Nem tudo na base
vem do dataset: o cache de classificações custou uma chamada de LLM por linha, e os
registros que sumiram do arquivo da Anvisa continuam lá, marcados como não vistos na última
coleta. Construir do zero jogaria os dois fora. Medido uma vez, sem a cópia: 109
classificações e 13 registros a menos, publicados sem um aviso. A cópia é feita pelo próprio
DuckDB (COPY FROM DATABASE), porque um cp pegaria o arquivo sem o WAL pendente.
uv run anvisa-cli sync --publicar --classificar # coleta, julga os novos e troca no fim
uv run anvisa-cli classificar --publicar --dias 365 # só enche o cache, sem recoletar
uv run anvisa-cli sync --publicar --forcar # aceita base menor que a servidaCom um connector lendo a base, use sempre --publicar, também no classificar: sem ele
a CLI tenta escrever direto no arquivo servido, o DuckDB recusa, e ela sai com código 1
dizendo que a classificação não foi gravada.
A publicação é recusada quando a base nova encolhe mais de 10%. Com a cópia acima, uma
coleta interrompida já não produz base pequena: ela só deixa de atualizar. A checagem fica
como rede de segurança para o que a cópia não cobre, como um clone que falhou pela metade
ou uma remoção em massa vinda da fonte. A versão trocada fica como .anterior, e
store.troca.reverter() volta atrás.
O repositório não traz a base pronta. Quem clona roda o próprio sync; quem hospeda um connector serve a sua. Os dados vêm do portal de dados abertos da Anvisa, que é público, então qualquer pessoa consegue montar a sua, mas a base construída e classificada é trabalho de organização, não faz parte do código.
Limitações conhecidas
O registro de dispositivos não tem campo de descrição. O texto que alimenta a
classificação é nome técnico + nome comercial + fabricante. É pouco. Um produto cujo nome
não diga o que ele faz será classificado com confiança baixa, e confiança baixa significa
"não dá para saber", não "não usa IA". Por isso a resposta traz indeterminados.
apenas_software=True troca recall por custo. No último ano há 1.832 registros Classe
III/IV e só 13 mencionam software. Sem esse pré-filtro por palavra-chave, uma varredura
dos 60 registros mais recentes encontra zero software, são cânulas, parafusos e testes
rápidos. Com ele, um produto que use IA sem dizer "software", "algoritmo" ou "CAD" fica de
fora. Use apenas_software=False para varrer tudo, ao custo de uma chamada de LLM por
registro.
A calibração de confiança do LLM é parcial. Modelos pequenos tendem a responder com confiança alta mesmo quando o texto não sustenta. O prompt fixa faixas de confiança por situação, o que melhorou bastante (reagentes saem com 0,9, nomes ambíguos com 0,2), mas não resolve por completo.
A base guarda o último estado visto, e não remove nada. O sync é upsert: um registro
que a Anvisa tira da publicação continua na base com a situação da última vez que apareceu.
Registro que sai da publicação costuma ter sido cancelado, então cada resultado traz
visto_na_ultima_coleta, e a resposta ganha um aviso quando algum vier false. Aconteceu
de verdade: entre 20 e 21/09/2026, 13 dispositivos sumiram do arquivo publicado.
anvisa-cli schema mostra a contagem, e o sync loga um aviso quando há registros assim.
Medicamentos sem número de registro não entram. O arquivo inclui produtos notificados (baixo risco), que não têm registro, e a pergunta "qual o status do registro" não se aplica a eles.
As URLs dos datasets podem mudar. Elas estão em data/sources.py, confirmadas em
20/09/2026 baixando os arquivos. Se uma sair do ar, o sync falha com mensagem explicando
como reconfirmar, em vez de baixar o arquivo errado em silêncio.
O DuckDB aceita um escritor por vez. Com --publicar, o sync e o classificar
escrevem numa cópia e as consultas seguem lendo a base servida o tempo todo. Sem
--publicar, enquanto a CLI escreve na base, as tools não conseguem abri-la e caem para
dados de exemplo, com um aviso que diz que a base está ocupada, não vazia.
A classificação cobre uma janela. Só os dispositivos dentro de --dias-classificacao
que passam no filtro de software recebem veredito na coleta. No connector oficial a janela
é de 10 anos; numa instância com o padrão, 365 dias.
Parte dos vereditos antigos não passou pela checagem de evidência. Eles vêm com
evidencia_confere: null até o sync com --reclassificar refazê-los. Essa passada parte
do próprio cache, sem janela nem filtro de software, então alcança também os vereditos
gravados por consultas com apenas_software=False. Se o LLM estiver fora nessa hora, o
veredito antigo continua servindo e volta na coleta seguinte.
Privacidade
Rodando local:
Sai da máquina: requisições HTTP para
dados.anvisa.gov.br, para baixar os CSVs públicos. Nada mais.Não sai: os termos que você consulta. A busca roda contra a base local.
O LLM é o seu: o texto dos registros vai para o endpoint que você configurou.
Sem telemetria, sem analytics, sem coleta de uso.
Pelo connector público, os termos e parâmetros de cada consulta chegam ao servidor, como em qualquer serviço remoto.
Contribuindo
Veja CONTRIBUTING.md. Em resumo: rode pytest, ruff e mypy antes do
PR, e não rode sincronizações em loop contra os dados abertos da Anvisa.
Licença e atribuição
Apache License 2.0: escolhida por tocar em regulação de dispositivo médico, onde a cláusula explícita de patente é mais protetiva.
Construído no contexto do IA.med.
Available Tools
2 toolsbuscar_samd_recentesA
Lista dispositivos médicos Classe III/IV registrados recentemente na Anvisa.
Cada registro traz um veredito sobre uso de IA ou aprendizado de máquina. A
Anvisa não publica esse campo: a classificação é heurística, feita por um LLM
local lendo o texto do registro, e cada item traz confiança, justificativa e
origem do veredito. Não apresente o veredito como fato regulatório.
O servidor não grava vereditos. No modo connector ele nem chama o LLM: serve
só o que a coleta diária já classificou (no deploy oficial, os registros dos
últimos 10 anos que passam no filtro de software). O que não está no cache vem
com origem='nao_classificado', que quer dizer "não se sabe", não "sem IA".
coletado_em diz quando a base foi atualizada pela última vez.
Args:
dias: tamanho da janela, em dias, a contar de hoje (1 a 3650).
apenas_com_ia: quando True, devolve só os classificados como usando IA.
limite: quantos registros analisar nesta chamada (1 a 200), dos mais
recentes para trás. Com truncado=true na resposta, sobraram registros
no período que nem foram olhados: a contagem da lista não serve para
dizer quantos existem.
apenas_software: quando True, analisa só registros cujo texto sugere software.
SaMD é raro no registro (13 de 1.832 registros Classe III/IV do último ano
mencionam software), então sem esse filtro a busca gasta as chamadas de LLM
em cânulas e parafusos. Desligue para varrer tudo, ao custo de ser lento.
| Name | Required | Description | Default |
|---|---|---|---|
| dias | No | ||
| limite | No | ||
| apenas_com_ia | No | ||
| apenas_software | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| dias | Yes | |
| aviso | No | |
| fonte | Yes | 'mock' = dado de exemplo, ainda não é registro real da Anvisa |
| total | Yes | Quantos dispositivos Classe III/IV existem na janela na base (só os que passam no filtro de software, quando apenas_software=True), antes do limite de análise e do filtro de IA. É o universo, não o tamanho desta lista: não use a contagem dos resultados para dizer quantos foram registrados no período. |
| truncado | No | True quando total > analisados: existem registros no período que esta chamada nem chegou a olhar. Aumente 'limite' ou reduza 'dias'. |
| analisados | No | Quantos registros a tool efetivamente avaliou nesta chamada, no máximo 'limite'. Os mais recentes primeiro. |
| resultados | Yes | |
| retornados | No | Quantos sobraram em 'resultados' depois do filtro de IA |
| coletado_em | No | Quando a base local de dispositivos foi atualizada pela última vez a partir do arquivo da Anvisa (horário local do servidor). None quando a resposta é dado de exemplo. |
| apenas_com_ia | Yes | |
| indeterminados | No | Quantos registros ficaram de fora por terem sido classificados como sem IA com confiança baixa, ou seja, o texto não permitiu decidir, o que não é o mesmo que não usar IA |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full burden of behavioral disclosure. It transparently explains that the AI classification is heuristic, not a regulatory fact, that the server does not persist verdicts, that connector mode serves cache only, that origem='nao_classificado' means 'unknown' rather than 'no AI', and that truncado=true invalidates count-based conclusions.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is moderately long but every sentence adds operational value. It is well structured with a purpose paragraph, a behavioral caveat paragraph, and a bulleted Args section. No filler or redundant restatement of schema defaults is present.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity and the total absence of annotations and schema descriptions, this description is outstandingly complete. It covers purpose, data provenance, cache behavior, error-prone semantics, parameter tradeoffs, and what the response signals mean. The output schema exists, so it need not enumerate return fields, and it still references the key output fields (truncado, origem, coletado_em).
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so parameters were entirely undocumented by the schema. The description compensates fully: it explains dias as a rolling window, limite with the truncado caveat, apenas_com_ia as filtering to AI-related verdicts, and apenas_software with real-world frequency data and the LLM-cost tradeoff.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb and resource: 'Lista dispositivos médicos Classe III/IV registrados recentemente na Anvisa'. It clearly establishes the tool's niche—recently registered Class III/IV devices with heuristic AI/ML verdicts—which also distinguishes it from the sibling consultar_status_medicamento.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives practical usage guidance for parameters, such as when to keep apenas_software enabled to avoid wasting LLM calls and when to disable it for a full sweep. It does not explicitly name an alternative tool or state 'use this instead of X', but the purpose and domain are clear enough that no false choice remains.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
consultar_status_medicamentoA
Consulta o status do registro de um medicamento na Anvisa.
Busca por nome comercial, princípio ativo ou número de registro e devolve os registros que casam, com situação ('Ativo' ou 'Inativo', como o arquivo aberto da Anvisa publica), número de registro, data de vencimento do registro, empresa detentora e categoria.
Um princípio ativo comum tem centenas de registros, um por detentor e
apresentação: "dipirona" casa com 557. A resposta traz total (quantos casam
na base inteira) e retornados (quantos vieram aqui). Quando truncado é
True, não conte os resultados para dizer quantos existem, use total.
coletado_em diz quando a base foi atualizada pela última vez a partir do
arquivo da Anvisa; acima de 48 horas a resposta traz aviso.
Termo sem correspondência devolve lista vazia. Só quando a base local não existe, está vazia ou não pôde ser aberta a resposta vem com dados de exemplo e fonte='mock': nesse caso, não trate como informação regulatória.
Args: termo: nome comercial ou princípio ativo (trecho, sem precisar de acento), ex.: "dipirona", ou número de registro, com ou sem pontos, ex.: "1.0582.0010". O número de 13 dígitos da apresentação também serve. limite: quantos registros trazer, de 1 a 200. Aumente para ver além dos primeiros.
| Name | Required | Description | Default |
|---|---|---|---|
| termo | Yes | ||
| limite | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| aviso | No | |
| fonte | Yes | 'mock' = dado de exemplo, ainda não é registro real da Anvisa |
| total | Yes | Quantos registros casam com o termo na base inteira, não quantos vieram nesta resposta. Um princípio ativo comum tem centenas, um por detentor e apresentação. |
| truncado | No | True quando total > retornados. Os que vieram são os ativos e de nome mais curto; os demais existem e não estão aqui. Não conte os resultados para responder 'quantos registros existem': use 'total'. |
| resultados | Yes | |
| retornados | No | Quantos registros vieram em 'resultados', no máximo 'limite' |
| coletado_em | No | Quando a base local foi atualizada pela última vez a partir do arquivo da Anvisa (horário local do servidor). None quando a resposta é dado de exemplo. |
| termo_consultado | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries full burden and does so thoroughly: it discloses truncation via total/retornados/truncado, data freshness via coletado_em, the 48-hour warning, empty-list behavior, and the mock-data fallback with fonte='mock' that must not be treated as regulatory information.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The definition is front-loaded with the core purpose and then adds only high-value operational details in a clear structure with a bold warning and Args section. Every paragraph carries necessary information for correct invocation.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Despite no annotations, the description covers input semantics, output semantics (total/retornados/truncado), freshness, warnings, and fallback behavior, so an agent has everything needed to call the tool and interpret results correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0%, but the Args section fully compensates: termo is explained with examples (dipirona, 1.0582.0010) and the 13-digit presentation number, while limite is specified with the 1-200 range and the guidance to raise it for more records.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Description opens with 'Consulta o status do registro de um medicamento na Anvisa', naming a specific verb, resource, and regulatory scope. It enumerates search keys (nome comercial, princípio ativo, número de registro) and the returned fields, making it easy to distinguish from the sibling buscar_samd_recentes.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives rich practical context: partial/accent-insensitive searches, how to increase limite, and what to expect on no match. It does not explicitly contrast with the sibling buscar_samd_recentes, but the usage context is clear and no exclusions are needed.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
2 tool updates
- Changed
buscar_samd_recentes15 fields changed- added
Input schema / properties / dias / maximumAdded value: +3650 - added
Input schema / properties / dias / minimumAdded value: +1 - added
Input schema / properties / limiteAdded value: +{ + "default": 50, + "maximum": 200, + "minimum": 1, + "title": "Limite", + "type": "integer" +} - changed
Output schema / $defs / ClassificacaoIA / descriptionPrevious value: -"Veredito heurístico sobre uso de IA — leia junto com a justificativa."New value: +"Veredito heurístico sobre uso de IA, leia junto com a justificativa." - added
Output schema / $defs / ClassificacaoIA / properties / evidencia_confereAdded value: +{ + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "default": null, + "description": "False quando o modelo citou como evidência algo que não está no texto do registro. O veredito pode continuar certo, mas a justificativa é o mecanismo de controle desta tool, e justificativa inventada o desarma: não repasse a descrição do produto sem conferir. None nas classificações feitas antes desta checagem existir.", + "title": "Evidencia Confere" +} - changed
Output schema / $defs / ClassificacaoIA / properties / origem / enumPrevious value: -[ - "llm", - "cache", - "indisponivel" -]New value: +[ + "llm", + "cache", + "indisponivel", + "nao_classificado" +] - added
Output schema / $defs / DispositivoSaMD / properties / situacao / descriptionAdded value: +"Campo VALIDADE_REGISTRO_CADASTRO do dataset, como a Anvisa entrega. Ele mistura duas coisas: para a maioria vem 'VIGENTE', para o resto vem uma data de validade. Quando for data, ela está também em 'validade_registro', já estruturada." - added
Output schema / $defs / DispositivoSaMD / properties / validade_registroAdded value: +{ + "anyOf": [ + { + "format": "date", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Data de validade do registro, quando 'situacao' traz uma data em vez de 'VIGENTE'. None não significa registro sem prazo: significa que a fonte não deu data para este registro.", + "title": "Validade Registro" +} - changed
Output schema / $defs / DispositivoSaMD / properties / visto_na_ultima_coleta / descriptionPrevious value: -"False quando o registro não apareceu no arquivo da última coleta — a situação mostrada pode estar desatualizada"New value: +"False quando o registro não apareceu no arquivo da última coleta, a situação mostrada pode estar desatualizada" - added
Output schema / properties / analisadosAdded value: +{ + "default": 0, + "description": "Quantos registros a tool efetivamente avaliou nesta chamada, no máximo 'limite'. Os mais recentes primeiro.", + "title": "Analisados", + "type": "integer" +} - added
Output schema / properties / coletado_emAdded value: +{ + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Quando a base local de dispositivos foi atualizada pela última vez a partir do arquivo da Anvisa (horário local do servidor). None quando a resposta é dado de exemplo.", + "title": "Coletado Em" +} - changed
Output schema / properties / indeterminados / descriptionPrevious value: -"Quantos registros ficaram de fora por terem sido classificados como sem IA com confiança baixa — ou seja, o texto não permitiu decidir, o que não é o mesmo que não usar IA"New value: +"Quantos registros ficaram de fora por terem sido classificados como sem IA com confiança baixa, ou seja, o texto não permitiu decidir, o que não é o mesmo que não usar IA" - added
Output schema / properties / retornadosAdded value: +{ + "default": 0, + "description": "Quantos sobraram em 'resultados' depois do filtro de IA", + "title": "Retornados", + "type": "integer" +} - added
Output schema / properties / total / descriptionAdded value: +"Quantos dispositivos Classe III/IV existem na janela na base (só os que passam no filtro de software, quando apenas_software=True), antes do limite de análise e do filtro de IA. É o universo, não o tamanho desta lista: não use a contagem dos resultados para dizer quantos foram registrados no período." - added
Output schema / properties / truncadoAdded value: +{ + "default": false, + "description": "True quando total > analisados: existem registros no período que esta chamada nem chegou a olhar. Aumente 'limite' ou reduza 'dias'.", + "title": "Truncado", + "type": "boolean" +}
- Changed
consultar_status_medicamento11 fields changed- added
Input schema / properties / limiteAdded value: +{ + "default": 20, + "maximum": 200, + "minimum": 1, + "title": "Limite", + "type": "integer" +} - removed
Input schema / properties / nome_ou_principio_ativoRemoved value: -{ - "title": "Nome Ou Principio Ativo", - "type": "string" -} - added
Input schema / properties / termoAdded value: +{ + "title": "Termo", + "type": "string" +} - changed
Input schema / requiredPrevious value: -[ - "nome_ou_principio_ativo" -]New value: +[ + "termo" +] - added
Output schema / $defs / RegistroMedicamento / properties / data_situacao / descriptionAdded value: +"Data de vencimento do registro, do campo DATA_VENCIMENTO_REGISTRO do dataset da Anvisa. Não é a data em que a situação atual foi decidida." - changed
Output schema / $defs / RegistroMedicamento / properties / situacao / descriptionPrevious value: -"deferido, indeferido, caducado, em análise"New value: +"Como o dataset aberto da Anvisa entrega: 'Ativo' ou 'Inativo'. O arquivo não distingue cancelado, caducado ou vencido dentro de 'Inativo'." - changed
Output schema / $defs / RegistroMedicamento / properties / visto_na_ultima_coleta / descriptionPrevious value: -"False quando o registro não apareceu no arquivo da última coleta — a situação mostrada pode estar desatualizada"New value: +"False quando o registro não apareceu no arquivo da última coleta, a situação mostrada pode estar desatualizada" - added
Output schema / properties / coletado_emAdded value: +{ + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Quando a base local foi atualizada pela última vez a partir do arquivo da Anvisa (horário local do servidor). None quando a resposta é dado de exemplo.", + "title": "Coletado Em" +} - added
Output schema / properties / retornadosAdded value: +{ + "default": 0, + "description": "Quantos registros vieram em 'resultados', no máximo 'limite'", + "title": "Retornados", + "type": "integer" +} - added
Output schema / properties / total / descriptionAdded value: +"Quantos registros casam com o termo na base inteira, não quantos vieram nesta resposta. Um princípio ativo comum tem centenas, um por detentor e apresentação." - added
Output schema / properties / truncadoAdded value: +{ + "default": false, + "description": "True quando total > retornados. Os que vieram são os ativos e de nome mais curto; os demais existem e não estão aqui. Não conte os resultados para responder 'quantos registros existem': use 'total'.", + "title": "Truncado", + "type": "boolean" +}
2 tool updates
v0.1.0- First observed
buscar_samd_recentes - First observed
consultar_status_medicamento
TDQS
Scored across 2 tools
The two tools are clearly separated by target entity: one queries medicine registrations, the other lists recent Class III/IV medical devices with AI classification. There is no functional overlap or plausible confusion between them.
Both tool names follow a consistent Portuguese snake_case verb-first pattern: 'consultar_' and 'buscar_'. The structure is predictable and matches the action each tool performs.
With only two tools, the server feels thin for a domain as broad as Anvisa regulatory data. The tools are meaningful and well-scoped, but the count sits at the low end of the borderline range.
The server covers only two narrow queries: medicine status lookup and recent device listings. It lacks many obvious operations for a regulatory database, such as querying devices by name/registration, retrieving full registration details, or tracking changes over time.
Maintenance
Related MCP Connectors
Open-source alternative to Jusbrasil for AI: find lawsuits by name, CPF, CNPJ or case number and bui
Discover, resolve, and query official Brazilian economic data with semantic search and provenance.
FDA 510(k)s, PMAs, recalls & trials, linked: predicate search and review-time stats for AI agents.
Brazilian public data API for AI agents. BCB, IBGE, CVM, B3, compliance. x402 payments on Base.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceProvides comprehensive pharmaceutical intelligence by integrating real-time openFDA data with locally-cached Orange Book and Purple Book databases. It enables users to analyze drug safety, patents, generic equivalents, biosimilars, and regulatory information through natural language queries.3MIT
- FlicenseNot gradedqualityDmaintenanceEnables AI agents to query and analyze FDA adverse events, drug labels, medical device clearances, and other public health datasets through natural language commands.14-
- FlicenseNot gradedqualityDmaintenanceEnables AI agents to access FDA and ClinicalTrials.gov data for medical device compliance, adverse event monitoring, and regulatory due diligence.-
- AlicenseAqualityDmaintenanceEnables LLMs to search FDA drug labels and adverse event data via the OpenFDA API, supporting natural language queries for drug safety information.2MIT