npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@souyo/brave-mcp

v2.2.2

Published

Brave Search MCP Server (multi-key fork): round-robins multiple BRAVE_API_KEYs over stdio or HTTP to lift the 1 RPS limit.

Readme

Brave Search MCP Server (multi-key fork)

Fork of brave/brave-search-mcp-server that round-robins multiple Brave Search API keys to lift the 1-RPS ceiling. The upstream server accepts exactly one key (BRAVE_API_KEY) and is hard-limited to ~1 request/second; this fork accepts a comma-separated list and spreads every search across keys, so N keys ≈ N×RPS with no single key tripping 429.

It also adds Bearer token auth on the HTTP transport (the upstream HTTP endpoint is unauthenticated), so you can safely expose it over a network.

Everything else (all 8 tools, the multi-key/rate-limit behavior, STDIO/HTTP transports, DNS-rebinding guard, and 2020-12 JSON Schema advertising) matches upstream, apart from the argument-tolerance changes documented under Tools.

How the rate-limit lift works

  • BRAVE_API_KEY may contain multiple keys, comma-separated: KEY1,KEY2,KEY3.
  • Every request picks the next key round-robin (single shared cursor), so load is spread evenly.
  • A per-key sliding window refuses to send more than BRAVE_MCP_KEY_RPS requests/second on any one key (default 1).
  • If Brave still answers 429 for a key (bursts, per-key quota), the request transparently retries on the next key. Only when every configured key has failed does the call error out.
  • Aggregate throughput is therefore roughly N keys × per-key RPS.

BRAVE_API_KEY_FILE still works and takes precedence, but it holds a single key (it cannot hold a list).

BRAVE_MCP_KEY_RPS tunes per-key ceiling. Keep it at 1 unless your plan's per-key limit differs.

When api.search.brave.com is unreachable

If your client cannot connect to api.search.brave.com directly (e.g. fetch failed), pick one of:

1. Proxy — the package honors HTTPS_PROXY / HTTP_PROXY env vars (Node's global fetch normally ignores them; here they are applied via undici's ProxyAgent):

{
  "mcpServers": {
    "brave-search": {
      "command": "npx",
      "args": ["-y", "@souyo/brave-mcp"],
      "env": {
        "BRAVE_API_KEY": "key1,key2",
        "HTTPS_PROXY": "http://127.0.0.1:7890"
      }
    }
  }
}

2. Reverse proxy — point BRAVE_API_BASE_URL at any reverse proxy of api.search.brave.com (e.g. a Cloudflare Worker). All endpoints are routed through it; key rotation and rate-limit handling are unchanged. The proxy must forward the X-Subscription-Token header and query string verbatim:

{
  "mcpServers": {
    "brave-search": {
      "command": "npx",
      "args": ["-y", "@souyo/brave-mcp"],
      "env": {
        "BRAVE_API_KEY": "key1,key2",
        "BRAVE_API_BASE_URL": "https://your-brave-proxy.example.workers.dev"
      }
    }
  }
}

Deploy with Docker

Build once, then run with your keys and a server token:

cp .env.example .env   # fill in BRAVE_API_KEY list + MCP_SERVER_TOKEN
docker compose up -d --build

This publishes http://<host>:8080/mcp. Every client request must carry Authorization: Bearer <MCP_SERVER_TOKEN>.

Verify it's up:

curl -H "Authorization: Bearer $MCP_SERVER_TOKEN" \
     -H "Content-Type: application/json" \
     -H "Accept: application/json, text/event-stream" \
     -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"probe","version":"0"}}}' \
     http://localhost:8080/mcp

Without the token you get 401:

curl -i http://localhost:8080/mcp -X POST -H 'Content-Type: application/json' \
     -d '{}' | head -1   # HTTP/1.1 401 Unauthorized

Usage from MCP clients (Streamable HTTP)

Point your client at http://<host>:8080/mcp (URL** only** — note the path), with the Authorization header set to your token:

{
  "mcpServers": {
    "brave-search-multi": {
      "type": "http",
      "url": "http://localhost:8080/mcp",
      "headers": {
        "Authorization": "Bearer YOUR_MCP_SERVER_TOKEN"
      }
    }
  }
}

Claude Desktop claude_desktop_config.json:

{
  "mcpServers": {
    "brave-search-multi": {
      "type": "http",
      "url": "http://localhost:8080/mcp",
      "headers": {
        "Authorization": "Bearer YOUR_MCP_SERVER_TOKEN"
      }
    }
  }
}

Local development

npm install
npm run build
npm test

Run in HTTP mode with Bearer auth:

BRAVE_API_KEY=key1,key2 BRAVE_MCP_TRANSPORT=http MCP_SERVER_TOKEN=secret \
  BRAVE_MCP_HOST=0.0.0.0 node dist/index.js

Configuration reference

| Variable | Default | Purpose | |---|---|---| | BRAVE_API_KEY | – | Comma-separated Brave API keys (required) | | BRAVE_API_KEY_FILE | – | Path to a file holding a single key (takes precedence) | | BRAVE_MCP_KEY_RPS | 1 | Per-key requests-per-second ceiling | | BRAVE_API_BASE_URL | https://api.search.brave.com | Reverse-proxy base URL for the Brave API — for clients that cannot reach Brave directly and have no proxy. Invalid values fall back to the official endpoint | | HTTPS_PROXY / HTTP_PROXY | – | Proxy for Brave API requests (Node's global fetch ignores these by default; this package honors them via undici ProxyAgent) | | MCP_SERVER_TOKEN | – | Bearer token required on every HTTP request (leave unset to disable auth for local testing) | | BRAVE_MCP_TRANSPORT | stdio | stdio or http | | BRAVE_MCP_PORT | 8080 | HTTP port | | BRAVE_MCP_HOST | 127.0.0.1 | Bind host (0.0.0.0 inside Docker) | | BRAVE_MCP_STATELESS | true | Stateless HTTP mode | | BRAVE_MCP_LOG_LEVEL | info | Log level | | BRAVE_MCP_ENABLED_TOOLS | – | Whitelist tool names | | BRAVE_MCP_DISABLED_TOOLS | – | Blacklist tool names | | BRAVE_MCP_ALLOWED_ORIGINS | – | DNS-rebinding origin allowlist | | BRAVE_MCP_ALLOWED_HOSTS | – | DNS-rebinding host allowlist |

CLI flags --brave-api-key, --brave-api-key-file, --transport, --port, --host, --enabled-tools, --disabled-tools, --logging-level, --stateless, --allowed-origins, --allowed-hosts behave as upstream; --mcp-server-token was added for the Bearer token.

Tools

Same tool set as upstream: brave_web_search, brave_local_search, brave_video_search, brave_image_search, brave_news_search, brave_summarizer, brave_place_search, brave_llm_context. See the upstream README for full parameter docs.

Argument tolerance

Upstream is strict in ways that trip up LLM callers, which surface as -32602 Input validation error. This fork is deliberately lenient where the intent is unambiguous:

  • null for an optional argument means "not supplied". Models routinely send every advertised property, using null for the ones they have no value for. Optional properties now accept null and fall back to the documented default (or omit the parameter). null for a required property still fails.
  • freshness: "any" means "no time filter". The API expresses that by omitting the parameter, but every documented value is a discovery window, so "any" is accepted and normalized away. pd/pw/pm/py are matched case-insensitively and trimmed. Other unknown values still fail.
  • **country: "ALL" (no country restriction) is accepted by brave_web_search, brave_place_search and brave_llm_context, but not by brave_news_search / brave_video_search / brave_image_search, whose country is a free-form string. This asymmetry is pre-existing upstream behaviour and was left as-is, but it is a real trap: a model that learns "ALL" on one tool will have it rejected on another.
  • Fixed a typo'd parameter name in the brave_web_search description: it advertised results_filter instead of result_filter. Zod strips unknown keys, so that call used to succeed while silently dropping the filter. If you see successful-but-unfiltered results on an older build, this was why.

Genuine mistakes (empty or over-long query, count/offset outside the per-tool range, unknown enum values for country / search_lang / ui_lang / safesearch) are still rejected with a message naming the offending field. Note that count limits differ per tool: 1-20 for web/local, 1-50 for video/news/place/llm_context, 1-200 for images.

License

MIT (same as upstream).