Jan 24, 2025

Tutorial: Creating and Using a Brave Search MCP Server in Claude Desktop

Why MCP?

LLMs are typically siloed applications, with no access to external data or the ability to take actions on your behalf. Anthropic’s Model-Context Protocol (MCP) enables external “tools” or “servers” to be invoked by large language models (LLMs) such as Claude, bridging the gap between the AI and external data sources. This allows AI applications to not just pull from data sources to enrich the context with high quality content (read), but also take actions on the users behalf (write).

Overview

In this tutorial, you will build a complete MCP server in Python that enables Claude to query the Brave Search API, and integrate it into Claude Desktop so you can perform web searches right from your local Claude desktop application. We will go step by step, providing full code and explanation. Finally, we will demonstrate testing our application with a sample query and showing off what it can do!

Prerequisites

  1. Python 3.10 or higher

    You can check your Python version by running:

    python --version
  2. Claude Desktop (latest version)

    Ensure you have installed the latest version of Claude for Desktop on macOS or Windows.

  3. Brave Search API key

    You can obtain an API key from Brave (requires an account). Once you have your key, keep it at hand to insert into the environment variables in our configuration.

1. Project Setup

  1. Create the project directory

    mkdir brave-search
    cd
    
    
  2. Create and activate a Python virtual environment

    python -m

    Then activate it:

    Unix/macOS:

    source

    Windows:

  3. Install required packages

    We will be using httpx for making asynchronous HTTP requests and mcp (the Python MCP SDK) for quickly creating our MCP server:

2. Create the Server Code

Create a new file called brave.py in the brave-search directory. We will build it up bit by bit for clarity. Below is the complete listing, which you can copy in full if you prefer. However, let’s walk through the important parts step by step.

2.1 Imports and Initialisation

At the top of brave.py, add:

from typing import Any, Optional
import httpx
from mcp.server.fastmcp import FastMCP

# Create an MCP server named "brave-search"
mcp = FastMCP("brave-search")

# We'll later set the Brave API key from environment variables
BRAVE_API_BASE = "https://api.search.brave.com/res/v1"
BRAVE_API_KEY = None  # We'll override this at runtime
  • FastMCP("brave-search"): This name ("brave-search") must match the name you put in your Claude Desktop configuration.

  • BRAVE_API_KEY: Declared at the top, but we will fetch it from the environment later (to keep secrets secure).

2.2 Helper Function: make_brave_request

Next, define a helper function for contacting the Brave API. This wraps httpx.AsyncClient calls and adds any required headers:

async def make_brave_request(url: str) -> Optional[dict[str, Any]]:
    """
    Make a request to the Brave API with proper error handling.
    Must have a valid BRAVE_API_KEY set.
    """
    if not BRAVE_API_KEY:
        raise ValueError("BRAVE_API_KEY environment variable is required")

    headers = {
        "Accept": "application/json",
        "Accept-Encoding": "gzip",
        "X-Subscription-Token": BRAVE_API_KEY
    }

    async with httpx.AsyncClient() as client:
        try:
            response = await client.get(url, headers=headers, timeout=30.0)
            response.raise_for_status()
            return response.json()
        except Exception as e:
            # In production, you might log this exception
            return None

2.3 Formatting the Web Search Results

We will need a utility function to format the JSON results from Brave into a simple string for display:

def format_web_results(data: dict[str, Any]) -> str:
    """Format web search results into readable text."""
    if not data.get("web", {}).get("results"):
        return "No results found."

    results = []
    for result in data["web"]["results"]:
        results.append(
            f"""Title: {result.get('title', '')}
Description: {result.get('description', '')}
URL: {result.get('url', '')}"""
        )

    return "\\n\\n".join(results)

2.4 Creating the Actual MCP Tool

We want to expose a single tool, brave_web_search, so that Claude can call it with a query, count, and offset. That way, when you type a question into Claude, the model can decide to use this tool if it needs search data:

@mcp.tool()
async def brave_web_search(query: str, count: int = 10, offset: int = 0) -> str:
    """
    Performs a web search using the Brave Search API.

    Args:
        query: Search query (max 400 chars, 50 words)
        count: Number of results (1-20, default 10)
        offset: Pagination offset (max 9, default 0)
    """
    # Ensure count is within bounds
    count = min(max(1, count), 20)
    offset = min(max(0, offset), 9)

    # Build URL with parameters
    url = f"{BRAVE_API_BASE}/web/search"
    params = {
        "q": query,
        "count": count,
        "offset": offset
    }
    url = f"{url}?{'&'.join(f'{k}={v}' for k, v in params.items())}"

    data = await make_brave_request(url)
    if not data:
        return "Unable to fetch search results."

    return format_web_results(data)

2.5 Putting It All Together: if __name__ == "__main__":

Finally, at the bottom of brave.py, we set the global BRAVE_API_KEY from the environment (so we never hard‑code it) and then start the server:

if __name__ == "__main__":
    import os

    # Get API key from environment
    BRAVE_API_KEY = os.getenv("BRAVE_API_KEY")
    if not BRAVE_API_KEY:
        print("Error: BRAVE_API_KEY environment variable is required")
        exit(1)

    # Initialise and run the server using stdio transport
    mcp.run(transport='stdio')

Your final brave.py file should look like this (all the snippets above, in order). Once ready, save it.

3. Configure Claude Desktop

With our MCP server code in place, we next tell Claude Desktop how to launch it.

  1. Open the Claude Desktop configuration file

    The file location depends on your operating system:

    • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json

    • Windows: %APPDATA%\\Claude\\claude_desktop_config.json

    If you have VSCode you can open it the config file into your editor witht he following command: code ~/Library/Application\\ Support/Claude/claude_desktop_config.json


  2. Add the MCP server configuration

    Insert or update the mcpServers section of your JSON. Below is an example:

    {
      "mcpServers": {
        "brave-search": {
          "command": "/ABSOLUTE/PATH/TO/.venv/bin/python",
          "args": [
            "-u",
            "/ABSOLUTE/PATH/TO/brave-search/brave.py"
          ],
          "env": {
            "BRAVE_API_KEY": "YOUR-REAL-API-KEY-HERE"
          }
        }
      }
    }

    Important points:

    • "brave-search" in the config must match FastMCP("brave-search") in brave.py.

    • Use absolute paths for both the Python interpreter (within the virtual environment) and the brave.py script.

    • The u flag ensures unbuffered output. This helps Claude Desktop properly capture the MCP server’s log output in real time.

    • We provide BRAVE_API_KEY via the env object so it is not hard‑coded in the source.


  3. Restart Claude Desktop

    After saving your claude_desktop_config.json changes, fully quit and restart Claude Desktop. Upon relaunch, it should read your new configuration and attempt to start the “brave-search” MCP server automatically.

4. Testing the Brave Search MCP Server

Once Claude Desktop restarts, you can confirm if your server is recognised:

  1. Look for the Hammer Icon

    A hammer icon or “tools” icon should appear in the Claude Desktop interface once at least one MCP server is running successfully.

  2. Inspect Tools

    Clicking the hammer icon should show a list of tools provided by the servers. Here, you should see a tool named brave_web_search.


  1. Try a Test Query

Go back to the main chat input in Claude Desktop and type something that might trigger the search. For example:

“What is the latest RL model by DeepSeek?”

Claude may attempt to call brave_web_search with this query. You should see a message from Claude indicating it is “Making a tool request: brave_web_search”. If everything is working, it will present you with the Brave search results.

Viewing Detailed Logs

If you need to examine the server’s logs for debugging or if the server fails to start, you can do:

tail -n 20 -F

(on macOS). On Windows, logs typically reside under %UserProfile%\\AppData\\Roaming\\Claude\\Logs\\. You may also go to Claude Desktop → Settings → Developer to open the logs folder. For failing MCP servers, a dedicated logs subfolder is generated, allowing you to see full tracebacks and debug messages.

5. Debugging and Troubleshooting

5.1 Common Issues

  1. ModuleNotFoundError

    • Ensure your virtual environment is active when installing packages.

    • Verify by running pip list | grep mcp (Unix/macOS) or pip list | findstr fastmcp (Windows).

  2. Server Disconnection

    • Make sure all paths in claude_desktop_config.json are absolute.

    • Verify the Python path is correct.

    • Check if brave.py exists where you specified.

  3. BRAVE_API_KEY not found

    • Double check you placed the BRAVE_API_KEY in the env object of your config.

    • Confirm the key is valid.

  4. No Tools Visible

    • Has Claude Desktop properly restarted after adding your new config?

    • Check the hammer icon at the top of the chat interface.

5.2 Detailed Logs in Claude Desktop

You can inspect advanced logs and debug MCP servers by going to Settings → Developer within Claude Desktop. There, you will find full debug logs for each MCP server. If you see a log folder titled something like mcp_brave-search_2025-01-21_…, that will contain the standard error and standard output logs.

5.3 Testing from Scratch

If you suspect the server is not even starting, you can launch brave.py manually in a terminal:

cd

If that runs fine and waits, then the server is OK on its own—so any issues must lie in the Desktop config or the environment variables.

6. Sample End‑to‑End Usage

To demonstrate a complete cycle:

  1. Launch/Restart Claude Desktop

    Wait until Claude logs that the “brave-search” server is successfully connected.

  2. Enter Query

    In Claude’s chat input, type (for example):

    “What is the latest RL model by DeepSeek?”

  3. Behind the Scenes

    • Claude analyses your prompt.

    • Decides it needs to use the “brave_web_search” tool.

    • Calls your MCP server, passing the query string.

    • The server calls the Brave API, receives a JSON payload, and formats the result.

    • The result is returned to Claude, which then writes its final answer.

  4. View the Results

    Claude will generate a response, aided by the websites it has visited with the help of the MCP tool.

If you want to see even more detail about what Claude is doing, you can enable developer tools in Claude Desktop and open logs or watch the console output. This level of inspection is particularly useful if you’re building advanced MCP servers or diagnosing subtle integration problems.

Conclusion

Congratulations! You have built a fully functional MCP server in Python that brings Brave Search capabilities into your Claude Desktop environment. You can now further customise your MCP server by adding new tools or integrating other external APIs (e.g. weather, local file access, or advanced search features).

At Valyu, we are excited by tools such as MCP because they align with our vision to make high quality content and knowledge both accessible and monetisable for AI.

We’re excited to release an updated Valyu Context Enrichment API, in the coming weeks, with a Native MCP integration following soon after - where you’ll be able to hook up Valyu data to existing supported chat model such as Claude.

Feel free to drop @yorkeccak a message on X if you have any questions.

More to read

Subscribe to our newsletter!

Valyu is a data provenance and licensing platform that connects data providers with ML engineers looking for diverse, high-quality datasets for training models.  

#WeBuild 🛠️

Subscribe to our newsletter!

Valyu is a data provenance and licensing platform that connects data providers with ML engineers looking for diverse, high-quality datasets for training models.  

#WeBuild 🛠️

Subscribe to our newsletter!

Valyu is a data provenance and licensing platform that connects data providers with ML engineers looking for diverse, high-quality datasets for training models.  

#WeBuild 🛠️