
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
a) Python 3.10 or higher
You can check your Python version by running:
1python --version
b) Claude Desktop (latest version)
Ensure you have installed the latest version of Claude for Desktop on macOS or Windows.
c) 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. Prerequisites
1) Create the project directory
1mkdir brave-search2cd brave-search
2) Create and activate a Python virtual environment
1python -m venv .venv
Then activate it:
1#Linux/Unix2source .venv/bin/activate3#Windows4.venv\\Scripts\\activate
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:
1pip install httpx mcp
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:
1from typing import Any, Optional2import httpx3from mcp.server.fastmcp import FastMCP45# Create an MCP server named "brave-search"6mcp = FastMCP("brave-search")78# We'll later set the Brave API key from environment variables9BRAVE_API_BASE = "https://api.search.brave.com/res/v1"10BRAVE_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:
1async def make_brave_request(url: str) -> Optional[dict[str, Any]]:2 """3 Make a request to the Brave API with proper error handling.4 Must have a valid BRAVE_API_KEY set.5 """6 if not BRAVE_API_KEY:7 raise ValueError("BRAVE_API_KEY environment variable is required")89 headers = {10 "Accept": "application/json",11 "Accept-Encoding": "gzip",12 "X-Subscription-Token": BRAVE_API_KEY13 }1415 async with httpx.AsyncClient() as client:16 try:17 response = await client.get(url, headers=headers, timeout=30.0)18 response.raise_for_status()19 return response.json()20 except Exception as e:21 # In production, you might log this exception22 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:
1def format_web_results(data: dict[str, Any]) -> str:2 """Format web search results into readable text."""3 if not data.get("web", {}).get("results"):4 return "No results found."56 results = []7 for result in data["web"]["results"]:8 results.append(9 f"""Title: {result.get('title', '')}10Description: {result.get('description', '')}11URL: {result.get('url', '')}"""12 )1314 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:
1@mcp.tool()2async def brave_web_search(query: str, count: int = 10, offset: int = 0) -> str:3 """4 Performs a web search using the Brave Search API.56 Args:7 query: Search query (max 400 chars, 50 words)8 count: Number of results (1-20, default 10)9 offset: Pagination offset (max 9, default 0)10 """11 # Ensure count is within bounds12 count = min(max(1, count), 20)13 offset = min(max(0, offset), 9)1415 # Build URL with parameters16 url = f"{BRAVE_API_BASE}/web/search"17 params = {18 "q": query,19 "count": count,20 "offset": offset21 }22 url = f"{url}?{'&'.join(f'{k}={v}' for k, v in params.items())}"2324 data = await make_brave_request(url)25 if not data:26 return "Unable to fetch search results."2728 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:
1if __name__ == "__main__":2 import os34 # Get API key from environment5 BRAVE_API_KEY = os.getenv("BRAVE_API_KEY")6 if not BRAVE_API_KEY:7 print("Error: BRAVE_API_KEY environment variable is required")8 exit(1)910 # Initialise and run the server using stdio transport11 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:
1{2 "mcpServers": {3 "brave-search": {4 "command": "/ABSOLUTE/PATH/TO/.venv/bin/python",5 "args": [6 "-u",7 "/ABSOLUTE/PATH/TO/brave-search/brave.py"8 ],9 "env": {10 "BRAVE_API_KEY": "YOUR-REAL-API-KEY-HERE"11 }12 }13 }14}
Important bits:
- "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:
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.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.


Viewing Detailed Logs
If you need to examine the server’s logs for debugging or if the server fails to start, you can do:
1tail -n 20 -F ~/Library/Logs/Claude/mcp*.log
(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
- 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).
- 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.
- 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.
- 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:
1cd /ABSOLUTE/PATH/TO/brave-search2.venv/bin/python brave.py
6. Sample End‑to‑End Usage
To demonstrate a complete cycle:
Launch/Restart Claude Desktop
Wait until Claude logs that the “brave-search” server is successfully connected.Enter Query
In Claude’s chat input, type (for example):What is the latest RL model by DeepSeek?
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.
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.