Skip to content
Community listing

Brave Search MCP

v2.0.0

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.

transport:stdio runtime:npm license:MIT tools:5

Target client

run in your project directory

Held in this page only — never stored, logged, or sent anywhere but back to your screen.

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

All 5 tools, one page each

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.

Technical article

Generated September 19, 2026

Brave Search MCP Server: Complete Technical Guide to Privacy-Focused Web Search Integration for AI Applications

Executive Summary

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.

Part 1: Understanding Brave Search in the MCP Ecosystem

1.1 Why Brave Search Matters for AI Assistants

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:

  • Tracks user searches and builds profiles
  • Personalizes results based on browsing history
  • Sells targeting data to advertisers
  • Returns biased results based on user profile

Brave Search:

  • No tracking whatsoever
  • No user profiles or history
  • No personalization based on behavior
  • Completely independent index

For AI assistants, this matters because:

  1. Users don’t want their search queries logged
  2. Organizations want privacy-compliant search
  3. Reproducible results across different contexts
  4. GDPR/CCPA compliance built-in

1.2 Search Architecture Comparison

Google Search Integration (Traditional)

User Question
    ↓
Claude → Google API
    ↓
[Google tracks query, user profile]
    ↓
Results returned
    ↓
Claude → User

Problems:

  • Every search is tracked
  • Results vary based on user profile
  • Privacy concerns
  • Compliance issues

Brave Search via MCP (New Approach)

User Question
    ↓
Claude → Brave Search MCP Server
    ↓
[Privacy-focused, no tracking]
    ↓
Results returned
    ↓
Claude → User

Benefits:

  • No tracking
  • Consistent results
  • Privacy by default
  • Compliance-friendly

Part 2: Brave Search API Technical Specifications

2.1 API Endpoints and Parameters

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
  }
}

2.2 MCP Server Implementation

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

Part 3: Integration Patterns with Claude

3.1 Basic Integration Example

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)

3.2 Advanced Integration with Tool Use

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

Part 4: Performance Optimization & Caching

4.1 Response Caching Strategy

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

4.2 Rate Limiting

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

Part 5: Real-World Use Cases

5.1 Research Assistant

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
    }

5.2 Fact Checking

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
    }

Part 6: Deployment & Best Practices

6.1 Environment Configuration

# .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

6.2 Error Handling

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

Part 7: Privacy & Compliance

7.1 Privacy Guarantees

Brave Search provides:

  • No user tracking
  • No data collection
  • No behavioral profiles
  • No personalization based on history
  • GDPR compliant by design
  • CCPA compliant by design

7.2 Comparison Matrix

Feature Google 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

Conclusion

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.