mcp-web-search
README.md
# mcp-web-search
Servidor MCP (Model Context Protocol) para consultas na internet, publicado na **Vercel** como uma API HTTP stateless. Permite que agentes de IA como o **Antigravity** pesquisem na web, leiam páginas, consumam APIs REST e extraiam links — sem problemas de bloqueios anti-bot.
---
## Ferramentas disponíveis
| Ferramenta | Descrição |
|---|---|
| `web_search` | Busca na internet via **Tavily API** e retorna título, URL e snippet de cada resultado |
| `read_webpage` | Usa o Tavily Extract para baixar páginas burlando bloqueios anti-bot e retorna o conteúdo em Markdown (ou texto puro), com `query` opcional para trazer só os trechos relevantes |
| `fetch_json` | Faz GET em qualquer URL via Axios e retorna o JSON da resposta (ideal para APIs REST públicas) |
| `extract_links` | Usa o Tavily Extract para ler a página e extrai todos os links com suporte a filtro por domínio |
---
## Markdown, não HTML
As ferramentas devolvem **Markdown** porque é o formato mais eficiente para o modelo que consome o MCP: a mesma informação em uma fração dos tokens do HTML, com a hierarquia que importa (títulos, listas, tabelas, links) preservada e o layout descartado. Além disso, o `/extract` da Tavily aceita apenas `markdown` ou `text` — **HTML não existe no caminho**, por isso o antigo modo `raw_html` foi removido em vez de continuar prometendo algo que não entregava.
As saídas das tools também são Markdown compacto em vez de JSON indentado. Medido em páginas reais: `extract_links` caiu 54% (6.470 → 2.980 tokens em 163 links) e `web_search` 9%.
As imagens viram o próprio texto alternativo: a URL do arquivo não diz nada a quem vai ler a página, e caminhos de CDN passam fácil de 100 caracteres cada. No catálogo da MANN-FILTER isso sozinho corta 18% do conteúdo.
O maior ganho de contexto, porém, está no `query` do `read_webpage`: a Tavily reordena a página e devolve só os trechos pertinentes. Na Wikipédia pt do Brasil, a página inteira dá ~142.800 tokens; com `query: "população e economia do Brasil"` são **509 tokens** — e com `chunks_per_source: 1`, 153. Sem `query`, o conteúdo é cortado em `max_chars` (padrão 50.000 ≈ 12k tokens) e o corte é avisado no próprio texto, para o modelo saber que a página continua.
---
## Stack
- **Next.js 16** (App Router) — framework do servidor
- **mcp-handler** — adaptador HTTP para o protocolo MCP
- **@modelcontextprotocol/server** — SDK MCP v2
- **Tavily API** — mecanismo oficial de busca e extração de páginas (imune a bloqueios anti-bot)
- **axios** — cliente HTTP para a Tavily e para o `fetch_json`
- **Zod v4** — validação de schemas das ferramentas
- **Vercel Hobby** — hospedagem gratuita (timeout de 60s por request)
---
## Configurando a Tavily API
Como a Vercel e outros datacenters são frequentemente bloqueados por firewalls ao tentar raspar o conteúdo da web (scraping), este projeto utiliza o **Tavily**, que é uma API focada em IA que consegue ler páginas e buscar na internet de forma 100% confiável.
A chave é **obrigatória** — não há chave embutida no código. Sem variável de ambiente configurada, as ferramentas de busca falham com `Nenhuma chave da Tavily configurada`.
1. Crie uma conta gratuita em [tavily.com](https://tavily.com) (dá direito a 1.000 requisições/mês gratuitas).
2. Vá no painel da sua hospedagem (ex: **Vercel**) e adicione a Variável de Ambiente:
- `TAVILY_API_KEY`: `sua_chave_aqui`
3. Para rodar local, crie um `.env.local` na raiz com a mesma variável. O arquivo é ignorado pelo git.
### Múltiplas chaves com troca automática
Para não ficar sem busca quando uma conta gratuita atinge o limite, configure várias chaves separadas por vírgula:
- `TAVILY_API_KEYS`: `tvly-dev-chave1,tvly-dev-chave2,tvly-dev-chave3`
O servidor usa a primeira chave e, ao receber um erro de limite (`429` por minuto, `432`/`433` por cota do plano) ou de chave inválida (`401`/`403`), **repete a mesma requisição, com os mesmos parâmetros, na próxima chave** — tudo dentro da mesma chamada MCP. O cliente (a IA) recebe o resultado normalmente e não precisa refazer a busca.
Detalhes do comportamento:
- A chave que funcionou vira a padrão das chamadas seguintes, então não há troca desnecessária.
- A chave que estourou fica em espera antes de voltar ao pool: o valor do header `Retry-After` (ou 1 minuto) para `429`, 6 horas para cota do plano, 24 horas para chave inválida.
- Se a única chave disponível levar um `429` curto, o servidor **espera até 3 segundos e repete** em vez de devolver erro. Esperas maiores falham na hora, para não travar o cliente.
- Se todas as chaves estiverem em espera, ele ainda tenta a que libera primeiro — o tempo de espera é uma estimativa local, não um dado da Tavily.
- Erros que não são de limite (`400` de parâmetro inválido, `500`) sobem direto, sem queimar as outras chaves.
- Esgotado o pool, o erro lista o motivo de cada chave (ex: `chave #1 (432 This request exceeds your plan's set usage limit...)`).
`TAVILY_API_KEYS` e `TAVILY_API_KEY` podem ser usadas juntas — as chaves das duas são somadas no pool. A lógica fica em [lib/tavily.ts](lib/tavily.ts).
### Verificando
```bash
pnpm test:rotation # testa a rotação com respostas simuladas (não gasta crédito)
pnpm test:links # testa o parser de links do extract_links (não gasta crédito)
pnpm test:markdown # testa a limpeza de Markdown e o corte por max_chars (não gasta crédito)
pnpm check:keys # diz quais chaves do .env.local ainda têm saldo (1 crédito por chave)
pnpm check:tools # roda as três ferramentas contra a API real (~3 créditos)
```
---
## Deploy na Vercel
### Pré-requisitos
- Node.js 20+
- pnpm
### 1. Clone o repositório
```bash
git clone https://github.com/suportebono/mcp-web-search.git
cd mcp-web-search
pnpm install
```
### 2. Teste localmente
```bash
pnpm dev
```
O endpoint MCP estará disponível em `http://localhost:3000/api/mcp`.
Para inspecionar as ferramentas visualmente:
```bash
npx @modelcontextprotocol/inspector http://localhost:3000/api/mcp
```
### 3. Publique na Vercel
```bash
npx vercel
```
Após o deploy, você receberá uma URL no formato `https://mcp-web-search-xxx.vercel.app`. Não se esqueça de configurar o `TAVILY_API_KEY`!
---
## Configurando no Antigravity
Edite o arquivo `~/.gemini/config/mcp_config.json` (crie se não existir):
```json
{
"mcpServers": {
"web-search": {
"serverUrl": "https://seu-projeto.vercel.app/api/mcp"
}
}
}
```
Reinicie o Antigravity e verifique em **`... > MCP Servers`** — as 4 ferramentas devem aparecer listadas.
---
## Configurando em outros clientes MCP
### Claude Desktop / Cursor / Windsurf
Adicione ao arquivo de configuração do seu cliente:
```json
{
"mcpServers": {
"web-search": {
"url": "https://seu-projeto.vercel.app/api/mcp"
}
}
}
```
### Clientes que só suportam stdio
Use o pacote `mcp-remote` como proxy:
```json
{
"mcpServers": {
"web-search": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://seu-projeto.vercel.app/api/mcp"]
}
}
}
```
---
## Referência das ferramentas
### `web_search`
Busca na internet usando a API do Tavily.
**Parâmetros:**
| Nome | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| `query` | `string` | ✅ | Termos de busca |
| `num_results` | `number` | ❌ | Quantidade de resultados (padrão: 5) |
**Exemplo de retorno:**
```json
[
{
"title": "Next.js 15 — Blog",
"url": "https://nextjs.org/blog/next-15",
"snippet": "Next.js 15 introduces...",
"source": "nextjs.org"
}
]
```
---
### `read_webpage`
Lê e extrai o conteúdo de uma URL burlando paywalls e anti-bots usando o Tavily Extract.
**Parâmetros:**
| Nome | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| `url` | `string` | ✅ | URL da página |
| `mode` | `"markdown" \| "text" \| "raw_html"` | ❌ | Formato de saída (padrão: `markdown`) |
> Remove automaticamente scripts, estilos, anúncios, navegação e rodapé antes de retornar o conteúdo.
---
### `fetch_json`
Faz uma requisição GET via Axios e retorna o JSON bruto.
**Parâmetros:**
| Nome | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| `url` | `string` | ✅ | URL da API |
| `headers` | `Record<string, string>` | ❌ | Headers HTTP opcionais |
---
### `extract_links`
Usa o Tavily Extract para ler o HTML real da página (inclusive sites dinâmicos) e extrai todos os links.
**Parâmetros:**
| Nome | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| `url` | `string` | ✅ | URL da página |
| `filter` | `string` | ❌ | Filtra links que contenham essa substring (ex: `"github.com"`) |
**Exemplo de retorno:**
```json
[
{
"text": "Documentação",
"href": "/docs",
"absolute_url": "https://exemplo.com/docs"
}
]
```
---
## Estrutura do projeto
```
mcp-web-search/
├── app/
│ └── api/
│ └── mcp/
│ └── route.ts ← Endpoint MCP (GET + POST)
├── lib/
│ ├── mcp-server.ts ← Registro de todas as ferramentas
│ ├── tavily.ts ← Cliente Tavily com pool de chaves e troca no limite
│ └── tools/
│ ├── web-search.ts ← Busca via Tavily API
│ ├── read-webpage.ts ← Leitura e parsing de páginas (Tavily Extract)
│ ├── fetch-json.ts ← Fetch de APIs REST genéricas
│ └── extract-links.ts ← Extração de links via Tavily Extract HTML
├── next.config.ts
└── package.json
```
---
## Protocolo MCP suportado
Este servidor implementa a especificação **MCP 2026-07-28** via transporte **Streamable HTTP stateless**, compatível com clientes modernos. Também oferece fallback automático para clientes que usam a especificação 2025-era Streamable HTTP.
---
## Licença
MIT
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues