mcp-retrieval
Servidor MCP em Go com três ferramentas somente leitura: busca na web, busca de imagens e extração de páginas para Markdown. Não requer chaves de API nem contas — a busca é feita via DuckDuckGo Lite, a busca de imagens via Bing Images, e as páginas são extraídas com um parser de legibilidade. Transportes stdio e HTTP, licença MIT.
Documentação
mcp-retrieval
Um servidor MCP que dá a um LLM três ferramentas web: busca, busca de imagens e raspagem de páginas — sem necessidade de chaves de API.
Ferramentas · Início rápido · Configuração · Mecanismo de recuperação · Arquitetura · Contribuindo
O que é
mcp-retrieval é um servidor Model Context Protocol escrito em Go. Ele expõe capacidades de recuperação web a qualquer cliente compatível com MCP (Claude Desktop, agentes de IDE, aplicativos LLM personalizados) como três ferramentas somente leitura. Internamente, ele usa a biblioteca retrieval-go para pesquisar na web e buscar páginas, retornando resultados como Markdown limpo, pronto para ser entregue a um modelo.
A biblioteca não precisa de chaves de API: a busca web passa pelo DuckDuckGo Lite, a busca de imagens pelo Bing Images, e a busca de páginas executa o HTML por um extrator de legibilidade antes de convertê-lo em Markdown. Para permanecer confiável contra proteção de bots, ela se passa por navegadores reais no nível de TLS e pode rotacionar tanto impressões digitais de navegador quanto proxies — veja Mecanismo de recuperação.
Ambos os transportes suportados pelo SDK MCP estão disponíveis e expõem o mesmo conjunto de ferramentas:
- stdio — o cliente inicia o binário e conversa via stdin/stdout (o padrão, ideal para clientes de desktop).
- http — um servidor HTTP transmissível de longa duração (útil para implantações remotas/compartilhadas).
Ferramentas
| Ferramenta | Descrição |
|---|---|
web_search | Executa uma ou mais consultas em paralelo e retorna trechos deduplicados e reordenados por consulta, com links. |
web_search_images | Executa uma ou mais consultas de imagem em paralelo e retorna resultados de imagem deduplicados por consulta. |
web_scrape | Baixa uma ou mais páginas em paralelo e retorna o texto principal do artigo como Markdown. |
Todas as três são anotadas como somente leitura. Cada ferramenta retorna um payload JSON estruturado que corresponde ao seu esquema de saída; o SDK espelha o mesmo JSON no bloco de conteúdo de texto para clientes que não leem structuredContent.
web_search
| Parâmetro | Tipo | Padrão | Observações |
|---|---|---|---|
queries | []string | — | Obrigatório. Executado em paralelo. |
max_results | int | 5 | Trechos por consulta, limitados pela config max_results (20). |
timeout_ms | int64 | 5000 | Tempo limite de toda a chamada; limitado a [min, max] da config. |
date | string | — | Filtro de frescor: d (dia), w (semana), m (mês), y (ano). |
web_search_images
| Parâmetro | Tipo | Padrão | Observações |
|---|---|---|---|
queries | []string | — | Obrigatório. Executado em paralelo. |
max_images | int | 5 | Imagens por consulta, limitadas pela config max_images (10). |
timeout_ms | int64 | 5000 | Tempo limite de toda a chamada; limitado a [min, max] da config. |
date | string | — | Filtro de frescor: d / w / m / y. |
web_scrape
| Parâmetro | Tipo | Padrão | Observações |
|---|---|---|---|
urls | []string | — | Obrigatório. Baixado em paralelo. |
robots_txt | bool | false | Respeita o robots.txt da página. |
timeout_ms | int64 | 5000 | Tempo limite de toda a chamada; limitado a [min, max] da config. |
remove_links | bool | false | Remove links Markdown do texto. |
max_chars | int | 20000 | Trunca o texto da página em N caracteres, limitado pela config max_document_chars (20000). |
Ambas as listas
queries/urlssão limitadas amax_queries(10) itens por chamada. Consultas devem ter ≤ 512 caracteres; URLs ≤ 2048 caracteres e apenashttp/https.
Resultados e contagens
Cada chamada se distribui pela lista de entrada e retorna uma entrada por consulta/URL, cada uma com seu próprio status — success, failed ou timeout — de modo que uma falha parcial ainda retorna os itens que funcionaram.
count é o número de itens realmente retornados, e pode ser menor que o max_results / max_images solicitado: duplicatas dentro dos resultados de uma única consulta são removidas antes de o limite ser aplicado, e o upstream pode simplesmente ter menos itens para oferecer. Um count menor é um resultado normal, não um erro.
A deduplicação é por consulta, não entre consultas. Cada entrada é deduplicada individualmente, então um link encontrado por duas das consultas na mesma chamada aparece em ambas as entradas — deduplique a união você mesmo se precisar.
Erros
Falhas no nível da solicitação são retornadas como resultado de ferramenta com isError: true e uma mensagem em texto simples, não como erro JSON-RPC — o modelo lê a mensagem e pode corrigir a chamada por conta própria. Falhas por item nunca fazem isso; elas permanecem dentro do payload como status: "failed" / "timeout".
Uma chamada falha completamente apenas quando a entrada é rejeitada antes de qualquer trabalho começar, ou quando todos os itens falham:
| Mensagem | Significado |
|---|---|
invalid request | Os argumentos não passaram na validação. |
too many queries / too many urls | A lista excede MAX_QUERIES. |
query must not be empty | Uma consulta vazia, ou uma lista queries vazia. |
query is too long | Uma consulta excede 512 caracteres. |
invalid url | Uma URL está malformada, tem mais de 2048 caracteres, ou não é http/https. |
robots.txt denied | robots_txt: true e a página não permite busca. |
upstream service unavailable | O upstream respondeu com um código de status inesperado. |
every url failed to be scraped; the pages may be unreachable or hold no extractable text | Todas as URLs falharam. As causas individuais são registradas em stderr, não retornadas. |
every query failed; the search upstream may be unreachable | Todas as consultas falharam. |
internal server error | Qualquer coisa não classificada. |
As mensagens de falha total deliberadamente não distinguem tempos limite de outras causas: um lote misto pode falhar por vários motivos ao mesmo tempo, e o status por item já carrega esse detalhe sempre que pelo menos um item sobrevive.
Limitações conhecidas
web_scrapelida apenas com HTML. As páginas passam por um extrator de legibilidade, que precisa de marcação de artigo, então respostastext/plainnão produzem nada e retornam comostatus: "failed". Hosts de arquivos brutos são o caso comum:raw.githubusercontent.com,github.com/.../raw/...,cdn.jsdelivr.net. Raspe a página renderizada em vez do arquivo bruto.- A relevância de
web_search_imagesnão é garantida. Para algumas consultas, o Bing Images serve uma página que não é um conjunto de resultados, e ela é analisada como se fosse — a ferramenta então retorna imagens não relacionadas comstatus: "success". Trate os resultados de imagem como melhor esforço e verifique-os antes de mostrá-los a um usuário. - Sem JavaScript. As páginas são buscadas como estão; o conteúdo renderizado no lado do cliente é invisível para o extrator.
Início rápido
Instalação
Escolha o que se adequar — todos dão o mesmo servidor.
Contêiner (sem necessidade de toolchain Go):
docker pull ghcr.io/role1776/mcp-retrieval:latest
Binário pré-compilado — pegue o arquivo para sua plataforma na última versão, descompacte-o e coloque mcp-retrieval no seu PATH.
MCP Bundle — para clientes que instalam arquivos .mcpb, baixe mcp-retrieval_<version>_<os>_<arch>.mcpb da última versão e abra-o com seu cliente. O bundle carrega o binário compilado, então não precisa de Docker nem Go. Escolha o arquivo que corresponde ao seu SO e arquitetura de CPU: um bundle contém um único binário nativo.
A partir do código-fonte:
go install github.com/Role1776/mcp-retrieval/app/cmd/mcp-retrieval@latest # needs Go 1.25.5+
Ou compile o binário no local (o módulo Go fica em app/):
make build # -> bin/mcp-retrieval
Execução
# defaults: stdio transport, no configuration needed
./bin/mcp-retrieval
# with an explicit env file
./bin/mcp-retrieval -env /absolute/path/to/.env
A única flag é opcional:
| Flag | Significado |
|---|---|
-env | Caminho para um arquivo .env. Se omitido — ou se o arquivo não existir — o servidor inicia com os padrões e o que já estiver no ambiente. Não há busca implícita: sob stdio, o diretório de trabalho é escolhido pelo cliente MCP, então um padrão relativo seria imprevisível. |
Conectando um cliente MCP (stdio)
Aponte seu cliente para o binário compilado. Exemplo de configuração do Claude Desktop:
{
"mcpServers": {
"retrieval": {
"command": "/absolute/path/to/mcp-retrieval",
"env": {
"MAX_RESULTS": "20"
}
}
}
}
O bloco env é opcional — "command" sozinho é suficiente.
Conectando um cliente MCP (contêiner)
Execute a imagem em stdio. A configuração ainda viaja pelo bloco env, mas o Docker precisa de cada variável nomeada na linha de comando com -e para que ela alcance o processo:
{
"mcpServers": {
"retrieval": {
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "MAX_RESULTS",
"-e", "DEFAULT_TIMEOUT_MS",
"ghcr.io/role1776/mcp-retrieval:latest"
],
"env": {
"MAX_RESULTS": "20",
"DEFAULT_TIMEOUT_MS": "5000"
}
}
}
}
-i é obrigatório — sem ele, o contêiner não recebe stdin e o cliente vê o servidor morrer imediatamente. Clientes que instalam a partir do MCP Registry constroem essa invocação por conta própria e solicitam as variáveis declaradas em server.json.
Executando via HTTP
Defina MCP_TRANSPORT=http e o servidor escuta em SERVER_PORT em MCP_PATH (padrão http://localhost:8080/mcp).
Configuração
Tudo é configurado por meio de variáveis de ambiente, e cada valor é validado antes da inicialização: um valor não numérico ou não positivo é um erro de inicialização. Relações entre limites não são verificadas na inicialização — veja Limites. Variáveis já presentes no ambiente vencem um arquivo .env, então o bloco env de um cliente MCP sempre tem efeito. Cada campo tem um padrão sensato, então o servidor roda sem nenhuma configuração (transporte stdio).
Veja .env.example para a lista completa com seus valores padrão, pronta para copiar para .env.
Servidor MCP
| Env | Padrão | Observações |
|---|---|---|
MCP_TRANSPORT | stdio | stdio ou http. |
MCP_NAME | mcp-retrieval | Nome do servidor anunciado aos clientes. |
MCP_PATH | /mcp | Rota HTTP (somente transporte http). |
A versão anunciada aos clientes não é configurável: ela é gravada no binário no momento da compilação a partir da tag git.
Servidor HTTP (somente transporte http)
| Env | Padrão |
|---|---|
SERVER_PORT | 8080 |
SERVER_READ_TIMEOUT | 60s |
SERVER_WRITE_TIMEOUT | 60s |
Cliente HTTP e proxy
| Env | Padrão | Observações |
|---|---|---|
MAX_IDLE_CONNS_PER_HOST | 100 | Pooling de conexões HTTP. |
PROXY_HOST | — | Opcional. Se definido, as solicitações são roteadas por um proxy de sessão rotativa. |
PROXY_PORT | — | Obrigatório quando PROXY_HOST está definido. |
PROXY_SCHEME | — | Obrigatório quando PROXY_HOST está definido. |
PROXY_LOGIN | — | Obrigatório quando PROXY_HOST está definido. |
PROXY_PASSWORD | — | Obrigatório quando PROXY_HOST está definido. |
Quando um proxy está configurado, cada solicitação de saída recebe um id de sessão único anexado ao login, então o provedor upstream rotaciona o IP de saída por solicitação.
Limites
| Variável | Padrão |
|---|---|
MAX_QUERIES | 10 |
DEFAULT_RESULTS | 5 |
MAX_RESULTS | 20 |
DEFAULT_TIMEOUT_MS | 5000 |
MAX_TIMEOUT_MS | 10000 |
MIN_TIMEOUT_MS | 1000 |
DEFAULT_IMAGES | 5 |
MAX_IMAGES | 10 |
DEFAULT_DOCUMENT_CHARS | 20000 |
MAX_DOCUMENT_CHARS | 20000 |
Cada valor é verificado individualmente — deve ser maior que zero — mas os trios DEFAULT_*, MIN_* e MAX_* não são verificados entre si na inicialização. Um conjunto inconsistente não interrompe o servidor; ele é reconciliado por solicitação:
- um valor que o chamador omite, ou passa como zero ou negativo, recai no
DEFAULT_*correspondente; - o resultado é então limitado a
[MIN_*, MAX_*], de modo que umDEFAULT_*maior que seuMAX_*simplesmente produzMAX_*; - se
MIN_*excederMAX_*, o máximo vence.
O limite efetivo está, portanto, sempre dentro do máximo configurado, e uma configuração incorreta degrada para um servidor funcional em vez de uma falha na inicialização. A desvantagem é que ela degrada silenciosamente: um erro de digitação como MAX_RESULTS=2 em vez de 20 não gera aviso, apenas respostas menores e silenciosas. Vale a pena verificar esses valores quando os resultados parecerem truncados.
Registro
| Variável | Padrão | Notas |
|---|---|---|
LOG_MODE | local | local → manipulador de texto no nível de depuração; prod → manipulador JSON no nível de informação. Os logs vão para stderr. |
Arquitetura
O projeto segue uma estrutura limpa e em camadas. As dependências apontam para dentro em direção ao domínio, e cada camada se comunica com a próxima por meio de interfaces.
app/ the Go module: sources plus its build files
(Dockerfile, .dockerignore, .goreleaser.yaml)
cmd/mcp-retrieval/main.go entry point: parse flags, load config, run app
internal/
app/ wiring + lifecycle (build server, run, graceful shutdown)
config/ config loading (.env → env vars → validate)
domain/ core types (Query, Link, Document, Snippet, Image) and errors
dto/web/ request/response shapes for the MCP tools
transport/mcp/ MCP layer
router/ registers every tool group on the MCP server
web/ tool handlers
utils/ schema helpers and error → tool-result mapping
usecase/web/ business logic: validation, parallelism, timeouts, dedupe/limit/rerank
adapter/web/ retrieval-go client wiring (search, images, scrape, proxy)
pkg/ reusable building blocks (mcpserver, server, logger, validator)
Fluxo de solicitação para uma chamada de ferramenta:
MCP client → transport/mcp/web (handler) → usecase/web → adapter/web → retrieval-go → the web
↑ maps errors ↑ validates, fans out, limits results
Busca e raspagem (scrape) se distribuem pela lista de entrada concorrentemente e agregam resultados por item, cada um com seu próprio status (success, failed, timeout). Uma chamada só falha completamente quando todos os itens nela falham.
Mecanismo de recuperação
Todo o trabalho de rede é delegado a retrieval-go, configurado em app/internal/adapter/web. Vale a pena saber:
- Fontes. A busca na web usa DuckDuckGo Lite; a busca de imagens usa Bing Images; a busca de páginas executa o HTML bruto por um extrator de legibilidade e converte o artigo principal para Markdown (tabelas incluídas). Nenhuma chave de API de mecanismo de busca é necessária.
- Impersonação de navegador. O adaptador ativa
WithBrowserRotation(), então cada solicitação é enviada de um dos ~11 perfis reais de navegador escolhidos aleatoriamente. Cada perfil combina uma impressão digital TLS/JA3 genuína (via uTLS) com umUser-Agentcorrespondente e cabeçalhos de dica de cliente — Chrome 133/131/120 (Windows/macOS/Linux), Edge 131, Firefox 120 (Windows/macOS), Safari 18.4 (macOS) e iOS 18.4 Safari. Isso faz o tráfego parecer de navegadores comuns em vez de um cliente HTTP Go, o que mantém as fontes gratuitas acessíveis. - Rotação de proxy. Quando
PROXY_HOSTestá configurado, o adaptador instala uma fábrica de proxy que anexa umsession-<id>único ao nome de usuário do proxy em cada solicitação. Com um provedor de proxy residencial/rotativo baseado em sessão, isso gera um IP de saída novo por solicitação, distribuindo a carga e evitando limites de taxa. Sem proxy, as solicitações saem diretamente. - Tratamento de respostas. As respostas são descomprimidas de forma transparente (
gzip,br,zstd,deflate), e o keep-alive é desabilitado (WithDisableKeepAlive()) para que conexões em pool não fixem uma única impressão digital/IP entre solicitações.
Nada disso precisa de configuração para funcionar — os padrões acima são aplicados automaticamente. Apenas credenciais de proxy são extras opcionais.
Desenvolvimento
Todo o código Go está em app/, então use o makefile da raiz do repositório ou passe -C app para a cadeia de ferramentas:
make build # compile the binary
make test # run tests
go -C app build ./... # compile everything
go -C app test ./... # run tests
go -C app vet ./... # static checks
Veja CONTRIBUTING.md para diretrizes de pull-request.
Licença
Lançado sob a Licença MIT.