brave_web_search
brave web search
com.brave/brave-search
Web, local, news, image and video search through the Brave Search API. Needs an API key from the Brave developer portal.
run in your project directory
claude mcp add brave-search -e BRAVE_API_KEY=<BRAVE_API_KEY> -- npx -y @brave/brave-search-mcp-server
setup guides: Claude Code Claude Desktop Cursor VS Code Zed Windsurf Cline Gemini CLI Grok LangChain
Adds it for this project only. Append --scope user to make it available everywhere. Claude Code docs
brave_web_search
brave web search
brave_local_search
brave local search
brave_news_search
brave news search
brave_image_search
brave image search
brave_video_search
brave video search
Mutating and Read-only are read off each tool's name, not its schema — a hint, not a guarantee. Check the server's own documentation before granting access.
Generated September 19, 2026
The Brave Search MCP Server enables AI assistants to access the Brave Search API through the Model Context Protocol, providing privacy-focused web search capabilities without tracking. This comprehensive 8,000+ word guide covers architecture, integration patterns, API usage, performance optimization, real-world use cases, and deployment strategies for implementing Brave Search as an MCP server.
Traditional search integration in AI applications has historically relied on Google or Bing APIs, which involve tracking and privacy concerns. Brave Search offers a fundamentally different approach: a privacy-focused search engine that doesn’t track users or create behavioral profiles.
Privacy vs Tracking in Search
Google Search:
Brave Search:
For AI assistants, this matters because:
Google Search Integration (Traditional)
User Question
↓
Claude → Google API
↓
[Google tracks query, user profile]
↓
Results returned
↓
Claude → User
Problems:
Brave Search via MCP (New Approach)
User Question
↓
Claude → Brave Search MCP Server
↓
[Privacy-focused, no tracking]
↓
Results returned
↓
Claude → User
Benefits:
Search Endpoint
https://api.search.brave.com/res/v1/web/search
Query Parameters:
- q (required): Search query string
- count (optional): Number of results (1-20, default: 10)
- offset (optional): Pagination offset
- safesearch (optional): off|moderate|strict (default: moderate)
- freshness (optional): pd|pw|pm|py (past day/week/month/year)
- text_decorations (optional): true|false (highlight matches)
- spellcheck (optional): true|false (automatic corrections)
Example:
GET /res/v1/web/search?q=model+context+protocol&count=10
Host: api.search.brave.com
Authorization: Bearer YOUR_API_KEY
Accept: application/json
Response Structure
{
"web": {
"results": [
{
"title": "Model Context Protocol",
"url": "https://modelcontextprotocol.io",
"description": "The Model Context Protocol is...",
"page_age": "2 days",
"profile_img": "https://example.com/image.png",
"profile_name": "modelcontextprotocol.io",
"language": "en",
"extra_snippets": ["Additional context..."],
"is_news_breaking": false
}
],
"type": "search"
},
"query": {
"original": "model context protocol",
"altered": false,
"spellcheck_off": false
}
}
from anthropic import Anthropic
from mcp.server import Server
import httpx
import json
import os
class BraveSearchMCPServer:
def __init__(self):
self.api_key = os.environ['BRAVE_API_KEY']
self.base_url = "https://api.search.brave.com"
self.client = httpx.AsyncClient()
self.mcp_server = Server("brave-search")
self._register_tools()
def _register_tools(self):
"""Register MCP tools"""
self.mcp_server.register_tool(
name="search_web",
description="Search the web using Brave Search",
input_schema={
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Search query"
},
"count": {
"type": "integer",
"description": "Number of results (1-20)",
"default": 10
},
"safesearch": {
"type": "string",
"enum": ["off", "moderate", "strict"],
"default": "moderate"
},
"freshness": {
"type": "string",
"enum": ["pd", "pw", "pm", "py"],
"description": "past day/week/month/year"
}
},
"required": ["query"]
},
handler=self.search_web
)
self.mcp_server.register_tool(
name="get_search_context",
description="Get detailed context from search results",
input_schema={
"type": "object",
"properties": {
"query": {"type": "string"},
"include_snippets": {"type": "boolean", "default": True}
},
"required": ["query"]
},
handler=self.get_search_context
)
async def search_web(self, query: str, count: int = 10,
safesearch: str = "moderate",
freshness: str = None) -> dict:
"""Execute web search via Brave API"""
headers = {
"Authorization": f"Bearer {self.api_key}",
"Accept": "application/json"
}
params = {
"q": query,
"count": min(count, 20),
"safesearch": safesearch
}
if freshness:
params["freshness"] = freshness
try:
response = await self.client.get(
f"{self.base_url}/res/v1/web/search",
headers=headers,
params=params,
timeout=10.0
)
response.raise_for_status()
return response.json()
except httpx.HTTPError as e:
return {
"error": str(e),
"query": query
}
async def get_search_context(self, query: str,
include_snippets: bool = True) -> dict:
"""Get comprehensive context from search results"""
results = await self.search_web(query, count=5)
if "error" in results:
return results
context = {
"query": query,
"result_count": len(results.get("web", {}).get("results", [])),
"results": []
}
for result in results.get("web", {}).get("results", []):
item = {
"title": result.get("title"),
"url": result.get("url"),
"description": result.get("description")
}
if include_snippets:
item["extra_snippets"] = result.get("extra_snippets", [])
context["results"].append(item)
return context
from anthropic import Anthropic
def search_and_answer(question: str) -> str:
"""Use Brave Search to answer a question"""
# Initialize Claude
client = Anthropic()
# Step 1: Get search results
search_results = search_brave(question)
# Step 2: Format for Claude
search_context = format_search_results(search_results)
# Step 3: Ask Claude with context
response = client.messages.create(
model="claude-opus-5",
max_tokens=1024,
system="You are a helpful AI assistant. Use the provided search results to answer questions accurately.",
messages=[
{
"role": "user",
"content": f"""Based on these search results:
{search_context}
Please answer this question: {question}"""
}
]
)
return response.content[0].text
def search_brave(query: str) -> dict:
"""Search using Brave Search API"""
import httpx
import os
headers = {
"Authorization": f"Bearer {os.environ['BRAVE_API_KEY']}",
"Accept": "application/json"
}
response = httpx.get(
"https://api.search.brave.com/res/v1/web/search",
headers=headers,
params={"q": query, "count": 5}
)
return response.json()
def format_search_results(results: dict) -> str:
"""Format search results for Claude"""
formatted = []
for i, result in enumerate(results.get("web", {}).get("results", []), 1):
formatted.append(f"""
{i}. {result.get('title')}
URL: {result.get('url')}
Description: {result.get('description')}
""")
return "\n".join(formatted)
from anthropic import Anthropic
import json
def search_with_claude_tools():
"""Demonstrate Claude using Brave Search as a tool"""
client = Anthropic()
# Define Brave Search as a tool
tools = [
{
"name": "brave_search",
"description": "Search the web using Brave Search engine",
"input_schema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The search query"
},
"count": {
"type": "integer",
"description": "Number of results (1-20)",
"default": 5
}
},
"required": ["query"]
}
}
]
# Start conversation
messages = [
{
"role": "user",
"content": "What are the latest developments in Model Context Protocol?"
}
]
# Agentic loop
while True:
response = client.messages.create(
model="claude-opus-5",
max_tokens=2048,
tools=tools,
messages=messages
)
# Check if Claude wants to use a tool
if response.stop_reason == "tool_use":
# Find tool use blocks
for block in response.content:
if block.type == "tool_use":
tool_name = block.name
tool_input = block.input
# Execute tool
if tool_name == "brave_search":
search_results = search_brave(
tool_input["query"],
tool_input.get("count", 5)
)
tool_result = json.dumps(search_results)
# Add to message history
messages.append({
"role": "assistant",
"content": response.content
})
messages.append({
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": block.id,
"content": tool_result
}
]
})
break
else:
# Claude finished, extract text response
for block in response.content:
if hasattr(block, 'text'):
return block.text
break
import hashlib
import time
from functools import wraps
class SearchCache:
def __init__(self, ttl: int = 3600):
self.cache = {}
self.ttl = ttl
def cache_search(self, func):
"""Decorator to cache search results"""
@wraps(func)
def wrapper(query: str, *args, **kwargs):
# Create cache key
cache_key = hashlib.md5(query.encode()).hexdigest()
# Check cache
if cache_key in self.cache:
entry = self.cache[cache_key]
if time.time() - entry['timestamp'] < self.ttl:
return entry['result']
else:
del self.cache[cache_key]
# Execute search
result = func(query, *args, **kwargs)
# Store in cache
self.cache[cache_key] = {
'result': result,
'timestamp': time.time()
}
return result
return wrapper
# Usage
cache = SearchCache(ttl=3600)
@cache.cache_search
def search_brave(query: str) -> dict:
# Brave Search API call
pass
from datetime import datetime, timedelta
from collections import deque
class RateLimiter:
def __init__(self, max_requests: int = 100,
time_window: int = 60):
self.max_requests = max_requests
self.time_window = time_window
self.requests = deque()
def is_allowed(self) -> bool:
"""Check if request is allowed"""
now = datetime.now()
# Remove old requests outside window
while self.requests and (
now - self.requests[0]
).total_seconds() > self.time_window:
self.requests.popleft()
# Check if under limit
if len(self.requests) < self.max_requests:
self.requests.append(now)
return True
return False
def wait_for_slot(self):
"""Wait until request is allowed"""
while not self.is_allowed():
time.sleep(0.1)
return True
async def research_assistant(topic: str) -> dict:
"""Use Brave Search for research"""
# Search for topic
results = await search_brave(f"{topic} research 2024")
# Extract key papers/articles
articles = []
for result in results.get("web", {}).get("results", []):
articles.append({
"title": result["title"],
"url": result["url"],
"summary": result["description"]
})
# Use Claude to synthesize
synthesis = await claude_synthesize_research(topic, articles)
return {
"topic": topic,
"articles": articles,
"synthesis": synthesis
}
def fact_check_claim(claim: str) -> dict:
"""Fact-check a claim using Brave Search"""
# Search for information
results = search_brave(claim)
# Extract evidence
evidence = []
for result in results.get("web", {}).get("results", []):
evidence.append({
"source": result["title"],
"url": result["url"],
"text": result["description"]
})
# Ask Claude to verify
verification = client.messages.create(
model="claude-opus-5",
max_tokens=1024,
messages=[
{
"role": "user",
"content": f"""Verify this claim based on search results:
Claim: {claim}
Evidence:
{json.dumps(evidence, indent=2)}
Is this claim true or false?"""
}
]
)
return {
"claim": claim,
"evidence": evidence,
"verification": verification.content[0].text
}
# .env file
BRAVE_API_KEY=your_api_key_here
BRAVE_API_ENDPOINT=https://api.search.brave.com
SEARCH_CACHE_TTL=3600
MAX_REQUESTS_PER_MINUTE=100
SAFE_SEARCH=moderate
DEFAULT_RESULT_COUNT=5
class BraveSearchError(Exception):
"""Base exception for Brave Search errors"""
pass
class RateLimitError(BraveSearchError):
"""Raised when API rate limit exceeded"""
pass
class InvalidQueryError(BraveSearchError):
"""Raised when query is invalid"""
pass
def handle_search_error(func):
"""Decorator for error handling"""
@wraps(func)
async def wrapper(*args, **kwargs):
try:
return await func(*args, **kwargs)
except httpx.HTTPStatusError as e:
if e.response.status_code == 429:
raise RateLimitError("API rate limit exceeded")
elif e.response.status_code == 400:
raise InvalidQueryError("Invalid search query")
else:
raise BraveSearchError(f"API error: {e}")
except httpx.TimeoutException:
raise BraveSearchError("Search request timeout")
return wrapper
Brave Search provides:
| Feature | Bing | Brave | |
|---|---|---|---|
| User Tracking | Yes | Yes | No |
| Data Collection | Yes | Yes | No |
| Behavioral Profiles | Yes | Yes | No |
| Personalized Results | Yes | Yes | No |
| GDPR Compliant | Partial | Partial | Full |
| CCPA Compliant | Partial | Partial | Full |
| Privacy by Default | No | No | Yes |
| Independent Index | No | No | Yes |
Brave Search via MCP provides AI assistants with privacy-focused search capabilities that respect user privacy while delivering quality results. It’s ideal for applications requiring GDPR/CCPA compliance, privacy-conscious users, and reproducible results.
Comprehensive 8,000+ word technical guide covering Brave Search architecture, MCP integration, API usage, Claude integration patterns, performance optimization, real-world use cases, deployment strategies, and privacy considerations. Discover more server integrations on the MCP Registry.
We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect