Skip to content

Enhance Brave Search Tool with Custom Goggle Support #1650

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 37 additions & 6 deletions src/brave-search/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ An MCP server implementation that integrates the Brave Search API, providing bot
- `query` (string): Search terms
- `count` (number, optional): Results per page (max 20)
- `offset` (number, optional): Pagination offset (max 9)
- `goggles` (array of strings, optional): Apply custom re-ranking using Brave Goggles. Provide an array of Goggle URLs **or definitions**.
- **Finding Goggles:** Discover pre-made Goggles for various topics at [https://search.brave.com/goggles/discover](https://search.brave.com/goggles/discover).
- **Creating Goggles:** Learn how to create your own custom Goggles (hosted URL or definition) by following the guide at the [Brave Goggles Quickstart repository](https://github.com/brave/goggles-quickstart).
- *Example (URL):* To use the Rust programming goggle, pass its URL: `["https://raw.githubusercontent.com/brave/goggles-quickstart/main/goggles/rust_programming.goggle"]`.
- *Example (Definition):* You could also pass the raw goggle definition string directly in the array.
- **Note:** Default goggles (URLs or definitions) can also be set server-wide using the `BRAVE_GOGGLES` environment variable (see Configuration below).

- **brave_local_search**
- Search for local businesses and services
Expand All @@ -34,6 +40,13 @@ An MCP server implementation that integrates the Brave Search API, providing bot
2. Choose a plan (Free tier available with 2,000 queries/month)
3. Generate your API key [from the developer dashboard](https://api-dashboard.search.brave.com/app/keys)

### Setting Environment Variables

- `BRAVE_API_KEY` (Required): Your Brave Search API key.
- `BRAVE_GOGGLES` (Optional): A **JSON string array** containing Goggle URLs or definitions to apply by default to all `brave_web_search` requests. Goggles specified in the tool call arguments will be added to these defaults.
*Example (URL):* `export BRAVE_GOGGLES='["https://raw.githubusercontent.com/brave/goggles-quickstart/main/goggles/rust_programming.goggle"]'`
*Example (Mix):* `export BRAVE_GOGGLES='["https://raw.githubusercontent.com/brave/goggles-quickstart/main/goggles/rust_programming.goggle", "https://raw.githubusercontent.com/andresnowak/js_programming_goggle/main/javascript.goggle", "[!boost:5] $site=javascript.info [!boost:5] $site=react.dev"]'`

### Usage with Claude Desktop

Add this to your `claude_desktop_config.json`:
Expand All @@ -51,10 +64,13 @@ Add this to your `claude_desktop_config.json`:
"--rm",
"-e",
"BRAVE_API_KEY",
"-e",
"BRAVE_GOGGLES",
"mcp/brave-search"
],
"env": {
"BRAVE_API_KEY": "YOUR_API_KEY_HERE"
"BRAVE_API_KEY": "YOUR_API_KEY_HERE",
"BRAVE_GOGGLES": "[\"YOUR_DEFAULT_GOGGLE_URL_OR_DEFINITION\"]"
}
}
}
Expand All @@ -73,7 +89,8 @@ Add this to your `claude_desktop_config.json`:
"@modelcontextprotocol/server-brave-search"
],
"env": {
"BRAVE_API_KEY": "YOUR_API_KEY_HERE"
"BRAVE_API_KEY": "YOUR_API_KEY_HERE",
"BRAVE_GOGGLES": "[\"YOUR_DEFAULT_GOGGLE_URL_OR_DEFINITION\"]"
}
}
}
Expand All @@ -84,9 +101,9 @@ Add this to your `claude_desktop_config.json`:

For quick installation, use the one-click installation buttons below...

[![Install with NPX in VS Code](https://img.shields.io/badge/VS_Code-NPM-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=brave&inputs=%5B%7B%22type%22%3A%22promptString%22%2C%22id%22%3A%22apiKey%22%7D%5D&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40modelcontextprotocol%2Fserver-brave-search%22%5D%2C%22env%22%3A%7B%22BRAVE_API_KEY%22%3A%22%24%7Binput%3Abrave_api_key%7D%22%7D%7D) [![Install with NPX in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-NPM-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=brave&inputs=%5B%7B%22type%22%3A%22promptString%22%2C%22id%22%3A%22apiKey%22%7D%5D&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40modelcontextprotocol%2Fserver-brave-search%22%5D%2C%22env%22%3A%7B%22BRAVE_API_KEY%22%3A%22%24%7Binput%3Abrave_api_key%7D%22%7D%7D&quality=insiders)
[![Install with NPX in VS Code](https://img.shields.io/badge/VS_Code-NPM-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=brave&inputs=%5B%7B%22type%22%3A%22promptString%22%2C%22id%22%3A%22apiKey%22%2C%22description%22%3A%22Brave%20Search%20API%20Key%22%2C%22password%22%3Atrue%7D%2C%7B%22type%22%3A%22promptString%22%2C%22id%22%3A%22goggles%22%2C%22description%22%3A%22Optional%3A%20Enter%20your%20default%20Goggle%20URLs%2FDefinitions%22%7D%5D&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40modelcontextprotocol%2Fserver-brave-search%22%5D%2C%22env%22%3A%7B%22BRAVE_API_KEY%22%3A%22%24%7Binput%3Abrave_apiKey%7D%22%2C%22BRAVE_GOGGLES%22%3A%22%24%7Binput%3Abrave_goggles%7D%22%7D%7D) [![Install with NPX in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-NPM-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=brave&inputs=%5B%7B%22type%22%3A%22promptString%22%2C%22id%22%3A%22apiKey%22%2C%22description%22%3A%22Brave%20Search%20API%20Key%22%2C%22password%22%3Atrue%7D%2C%7B%22type%22%3A%22promptString%22%2C%22id%22%3A%22goggles%22%2C%22description%22%3A%22Optional%3A%20Enter%20your%20default%20Goggle%20URLs%2FDefinitions%22%7D%5D&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40modelcontextprotocol%2Fserver-brave-search%22%5D%2C%22env%22%3A%7B%22BRAVE_API_KEY%22%3A%22%24%7Binput%3Abrave_apiKey%7D%22%2C%22BRAVE_GOGGLES%22%3A%22%24%7Binput%3Abrave_goggles%7D%22%7D%7D&quality=insiders)

[![Install with Docker in VS Code](https://img.shields.io/badge/VS_Code-Docker-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=brave&inputs=%5B%7B%22type%22%3A%22promptString%22%2C%22id%22%3A%22apiKey%22%7D%5D&config=%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22-i%22%2C%22--rm%22%2C%22-e%22%2C%22BRAVE_API_KEY%22%2C%22mcp%2Fbrave-search%22%5D%2C%22env%22%3A%7B%22BRAVE_API_KEY%22%3A%22%24%7Binput%3Abrave_api_key%7D%22%7D%7D) [![Install with Docker in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Docker-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=brave&inputs=%5B%7B%22type%22%3A%22promptString%22%2C%22id%22%3A%22apiKey%22%7D%5D&config=%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22-i%22%2C%22--rm%22%2C%22-e%22%2C%22BRAVE_API_KEY%22%2C%22mcp%2Fbrave-search%22%5D%2C%22env%22%3A%7B%22BRAVE_API_KEY%22%3A%22%24%7Binput%3Abrave_api_key%7D%22%7D%7D&quality=insiders)
[![Install with Docker in VS Code](https://img.shields.io/badge/VS_Code-Docker-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=brave&inputs=%5B%7B%22type%22%3A%22promptString%22%2C%22id%22%3A%22apiKey%22%2C%22description%22%3A%22Brave%20Search%20API%20Key%22%2C%22password%22%3Atrue%7D%2C%7B%22type%22%3A%22promptString%22%2C%22id%22%3A%22goggles%22%2C%22description%22%3A%22Optional%3A%20Enter%20your%20default%20Goggle%20URLs%2FDefinitions%22%7D%5D&config=%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22-i%22%2C%22--rm%22%2C%22-e%22%2C%22BRAVE_API_KEY%22%2C%22-e%22%2C%22BRAVE_GOGGLES%22%2C%22mcp%2Fbrave-search%22%5D%2C%22env%22%3A%7B%22BRAVE_API_KEY%22%3A%22%24%7Binput%3Abrave_apiKey%7D%22%2C%22BRAVE_GOGGLES%22%3A%22%24%7Binput%3Abrave_goggles%7D%22%7D%7D) [![Install with Docker in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Docker-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=brave&inputs=%5B%7B%22type%22%3A%22promptString%22%2C%22id%22%3A%22apiKey%22%2C%22description%22%3A%22Brave%20Search%20API%20Key%22%2C%22password%22%3Atrue%7D%2C%7B%22type%22%3A%22promptString%22%2C%22id%22%3A%22goggles%22%2C%22description%22%3A%22Optional%3A%20Enter%20your%20default%20Goggle%20URLs%2FDefinitions%22%7D%5D&config=%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22-i%22%2C%22--rm%22%2C%22-e%22%2C%22BRAVE_API_KEY%22%2C%22-e%22%2C%22BRAVE_GOGGLES%22%2C%22mcp%2Fbrave-search%22%5D%2C%22env%22%3A%7B%22BRAVE_API_KEY%22%3A%22%24%7Binput%3Abrave_apiKey%7D%22%2C%22BRAVE_GOGGLES%22%3A%22%24%7Binput%3Abrave_goggles%7D%22%7D%7D&quality=insiders)

For manual installation, add the following JSON block to your User Settings (JSON) file in VS Code. You can do this by pressing `Ctrl + Shift + P` and typing `Preferences: Open User Settings (JSON)`.

Expand All @@ -105,6 +122,11 @@ Optionally, you can add it to a file called `.vscode/mcp.json` in your workspace
"id": "brave_api_key",
"description": "Brave Search API Key",
"password": true
},
{
"type": "promptString",
"id": "brave_goggles",
"description": "Optional: Enter your default Goggle URLs/Definitions"
}
],
"servers": {
Expand All @@ -116,10 +138,13 @@ Optionally, you can add it to a file called `.vscode/mcp.json` in your workspace
"--rm",
"-e",
"BRAVE_API_KEY",
"-e",
"BRAVE_GOGGLES",
"mcp/brave-search"
],
"env": {
"BRAVE_API_KEY": "${input:brave_api_key}"
"BRAVE_API_KEY": "${input:brave_api_key}",
"BRAVE_GOGGLES": "${input:brave_goggles}"
}
}
}
Expand All @@ -138,14 +163,20 @@ Optionally, you can add it to a file called `.vscode/mcp.json` in your workspace
"id": "brave_api_key",
"description": "Brave Search API Key",
"password": true
},
{
"type": "promptString",
"id": "brave_goggles",
"description": "Optional: Enter your default Goggle URLs/Definitions"
}
],
"servers": {
"brave-search": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-brave-search"],
"env": {
"BRAVE_API_KEY": "${input:brave_api_key}"
"BRAVE_API_KEY": "${input:brave_api_key}",
"BRAVE_GOGGLES": "${input:brave_goggles}"
}
}
}
Expand Down
48 changes: 41 additions & 7 deletions src/brave-search/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const WEB_SEARCH_TOOL: Tool = {
"Performs a web search using the Brave Search API, ideal for general queries, news, articles, and online content. " +
"Use this for broad information gathering, recent events, or when you need diverse web sources. " +
"Supports pagination, content filtering, and freshness controls. " +
"Can optionally apply one or more Brave Goggles (URLs) for custom re-ranking to modify the results of the query. " +
"Maximum 20 results per request, with offset for pagination. ",
inputSchema: {
type: "object",
Expand All @@ -32,6 +33,11 @@ const WEB_SEARCH_TOOL: Tool = {
description: "Pagination offset (max 9, default 0)",
default: 0
},
goggles: {
type: "array",
items: { type: "string" },
description: "Optional array of Goggle URLs or definitions for custom re-ranking. See https://github.com/brave/goggles-quickstart"
}
},
required: ["query"],
},
Expand Down Expand Up @@ -85,6 +91,21 @@ if (!BRAVE_API_KEY) {
process.exit(1);
}

// Optional default goggles from environment variable (JSON string array)
let DEFAULT_GOGGLES: string[] = [];
if (process.env.BRAVE_GOGGLES) {
try {
const parsedGoggles = JSON.parse(process.env.BRAVE_GOGGLES);
if (Array.isArray(parsedGoggles) && parsedGoggles.every(item => typeof item === 'string')) {
DEFAULT_GOGGLES = parsedGoggles.filter(Boolean); // Filter empty strings
} else {
console.error('Warning: BRAVE_GOGGLES environment variable is not a valid JSON string array. Ignoring.');
}
} catch (error) {
console.error(`Warning: Failed to parse BRAVE_GOGGLES environment variable as JSON: ${error}. Ignoring.`);
}
}

const RATE_LIMIT = {
perSecond: 1,
perMonth: 15000
Expand Down Expand Up @@ -156,15 +177,20 @@ interface BravePoiResponse {
}

interface BraveDescription {
descriptions: {[id: string]: string};
descriptions: { [id: string]: string };
}

function isBraveWebSearchArgs(args: unknown): args is { query: string; count?: number } {
function isBraveWebSearchArgs(args: unknown): args is { query: string; count?: number; offset?: number; goggles?: string[] } {
return (
typeof args === "object" &&
args !== null &&
"query" in args &&
typeof (args as { query: string }).query === "string"
typeof (args as { query: string }).query === "string" &&
((args as { count?: number }).count === undefined || typeof (args as { count?: number }).count === "number") &&
((args as { offset?: number }).offset === undefined || typeof (args as { offset?: number }).offset === "number") &&
((args as { goggles?: string[] }).goggles === undefined ||
(Array.isArray((args as { goggles?: string[] }).goggles) &&
(args as { goggles?: string[] }).goggles!.every(item => typeof item === 'string')))
);
}

Expand All @@ -177,13 +203,21 @@ function isBraveLocalSearchArgs(args: unknown): args is { query: string; count?:
);
}

async function performWebSearch(query: string, count: number = 10, offset: number = 0) {
async function performWebSearch(query: string, count: number = 10, offset: number = 0, goggles: string[] = []) {
checkRateLimit();
const url = new URL('https://api.search.brave.com/res/v1/web/search');
url.searchParams.set('q', query);
url.searchParams.set('count', Math.min(count, 20).toString()); // API limit
url.searchParams.set('offset', offset.toString());

// Merge default goggles from ENV with argument goggles
// No URL filtering needed here
const allGoggles = [...new Set([...DEFAULT_GOGGLES, ...goggles])];

if (allGoggles.length > 0) {
allGoggles.forEach(goggle => url.searchParams.append('goggles', goggle));
}

const response = await fetch(url, {
headers: {
'Accept': 'application/json',
Expand Down Expand Up @@ -232,7 +266,7 @@ async function performLocalSearch(query: string, count: number = 5) {
}

const webData = await webResponse.json() as BraveWeb;
const locationIds = webData.locations?.results?.filter((r): r is {id: string; title?: string} => r.id != null).map(r => r.id) || [];
const locationIds = webData.locations?.results?.filter((r): r is { id: string; title?: string } => r.id != null).map(r => r.id) || [];

if (locationIds.length === 0) {
return performWebSearch(query, count); // Fallback to web search
Expand Down Expand Up @@ -325,8 +359,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (!isBraveWebSearchArgs(args)) {
throw new Error("Invalid arguments for brave_web_search");
}
const { query, count = 10 } = args;
const results = await performWebSearch(query, count);
const { query, count = 10, offset = 0, goggles = [] } = args;
const results = await performWebSearch(query, count, offset, goggles);
return {
content: [{ type: "text", text: results }],
isError: false,
Expand Down