diff --git a/src/brave-search/README.md b/src/brave-search/README.md index 46213b6e5..9d6c6fda9 100644 --- a/src/brave-search/README.md +++ b/src/brave-search/README.md @@ -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 @@ -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`: @@ -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\"]" } } } @@ -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\"]" } } } @@ -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)`. @@ -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": { @@ -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}" } } } @@ -138,6 +163,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": { @@ -145,7 +175,8 @@ Optionally, you can add it to a file called `.vscode/mcp.json` in your workspace "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}" } } } diff --git a/src/brave-search/index.ts b/src/brave-search/index.ts index e0f616233..ff3915f1b 100644 --- a/src/brave-search/index.ts +++ b/src/brave-search/index.ts @@ -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", @@ -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"], }, @@ -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 @@ -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'))) ); } @@ -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', @@ -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 @@ -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,