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.

License MIT Go MCP Transport DuckDuckGo Bing Images uTLS

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

FerramentaDescrição
web_searchExecuta uma ou mais consultas em paralelo e retorna trechos deduplicados e reordenados por consulta, com links.
web_search_imagesExecuta uma ou mais consultas de imagem em paralelo e retorna resultados de imagem deduplicados por consulta.
web_scrapeBaixa 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âmetroTipoPadrãoObservações
queries[]stringObrigatório. Executado em paralelo.
max_resultsint5Trechos por consulta, limitados pela config max_results (20).
timeout_msint645000Tempo limite de toda a chamada; limitado a [min, max] da config.
datestringFiltro de frescor: d (dia), w (semana), m (mês), y (ano).

web_search_images

ParâmetroTipoPadrãoObservações
queries[]stringObrigatório. Executado em paralelo.
max_imagesint5Imagens por consulta, limitadas pela config max_images (10).
timeout_msint645000Tempo limite de toda a chamada; limitado a [min, max] da config.
datestringFiltro de frescor: d / w / m / y.

web_scrape

ParâmetroTipoPadrãoObservações
urls[]stringObrigatório. Baixado em paralelo.
robots_txtboolfalseRespeita o robots.txt da página.
timeout_msint645000Tempo limite de toda a chamada; limitado a [min, max] da config.
remove_linksboolfalseRemove links Markdown do texto.
max_charsint20000Trunca o texto da página em N caracteres, limitado pela config max_document_chars (20000).

Ambas as listas queries/urls são limitadas a max_queries (10) itens por chamada. Consultas devem ter ≤ 512 caracteres; URLs ≤ 2048 caracteres e apenas http/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 statussuccess, 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:

MensagemSignificado
invalid requestOs argumentos não passaram na validação.
too many queries / too many urlsA lista excede MAX_QUERIES.
query must not be emptyUma consulta vazia, ou uma lista queries vazia.
query is too longUma consulta excede 512 caracteres.
invalid urlUma URL está malformada, tem mais de 2048 caracteres, ou não é http/https.
robots.txt deniedrobots_txt: true e a página não permite busca.
upstream service unavailableO upstream respondeu com um código de status inesperado.
every url failed to be scraped; the pages may be unreachable or hold no extractable textTodas as URLs falharam. As causas individuais são registradas em stderr, não retornadas.
every query failed; the search upstream may be unreachableTodas as consultas falharam.
internal server errorQualquer 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_scrape lida apenas com HTML. As páginas passam por um extrator de legibilidade, que precisa de marcação de artigo, então respostas text/plain não produzem nada e retornam como status: "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_images nã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 com status: "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:

FlagSignificado
-envCaminho 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

EnvPadrãoObservações
MCP_TRANSPORTstdiostdio ou http.
MCP_NAMEmcp-retrievalNome do servidor anunciado aos clientes.
MCP_PATH/mcpRota 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)

EnvPadrão
SERVER_PORT8080
SERVER_READ_TIMEOUT60s
SERVER_WRITE_TIMEOUT60s

Cliente HTTP e proxy

EnvPadrãoObservações
MAX_IDLE_CONNS_PER_HOST100Pooling de conexões HTTP.
PROXY_HOSTOpcional. Se definido, as solicitações são roteadas por um proxy de sessão rotativa.
PROXY_PORTObrigatório quando PROXY_HOST está definido.
PROXY_SCHEMEObrigatório quando PROXY_HOST está definido.
PROXY_LOGINObrigatório quando PROXY_HOST está definido.
PROXY_PASSWORDObrigató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ávelPadrão
MAX_QUERIES10
DEFAULT_RESULTS5
MAX_RESULTS20
DEFAULT_TIMEOUT_MS5000
MAX_TIMEOUT_MS10000
MIN_TIMEOUT_MS1000
DEFAULT_IMAGES5
MAX_IMAGES10
DEFAULT_DOCUMENT_CHARS20000
MAX_DOCUMENT_CHARS20000

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 um DEFAULT_* maior que seu MAX_* simplesmente produz MAX_*;
  • se MIN_* exceder MAX_*, 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ávelPadrãoNotas
LOG_MODElocallocal → 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 um User-Agent correspondente 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_HOST está configurado, o adaptador instala uma fábrica de proxy que anexa um session-<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.