Updated Brave

This commit is contained in:
Mahesh Murag
2024-11-20 22:15:01 -05:00
parent 016f885a9c
commit 7e89d8323f
3 changed files with 88 additions and 170 deletions

61
package-lock.json generated
View File

@@ -12,6 +12,7 @@
"src/*"
],
"dependencies": {
"@modelcontextprotocol/server-brave-search": "*",
"@modelcontextprotocol/server-duckduckgo": "*",
"@modelcontextprotocol/server-everything": "*",
"@modelcontextprotocol/server-filesystem": "*",
@@ -162,6 +163,10 @@
"zod": "^3.23.8"
}
},
"node_modules/@modelcontextprotocol/server-brave-search": {
"resolved": "src/brave-search",
"link": true
},
"node_modules/@modelcontextprotocol/server-duckduckgo": {
"resolved": "src/duckduckgo",
"link": true
@@ -3607,6 +3612,62 @@
"zod": "^3.23.3"
}
},
"src/brave-search": {
"name": "@modelcontextprotocol/server-brave-search",
"version": "0.1.0",
"license": "MIT",
"dependencies": {
"@modelcontextprotocol/sdk": "0.5.0",
"jsdom": "^24.1.3",
"node-fetch": "^3.3.2"
},
"bin": {
"mcp-server-brave-search": "dist/index.js"
},
"devDependencies": {
"@types/jsdom": "^21.1.6",
"@types/node": "^20.10.0",
"shx": "^0.3.4",
"typescript": "^5.6.2"
}
},
"src/brave-search/node_modules/@types/node": {
"version": "20.17.6",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.6.tgz",
"integrity": "sha512-VEI7OdvK2wP7XHnsuXbAJnEpEkF6NjSN45QJlL4VGqZSXsnicpesdTWsg9RISeSdYd3yeRj/y3k5KGjUXYnFwQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.19.2"
}
},
"src/brave-search/node_modules/data-uri-to-buffer": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
"integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
"license": "MIT",
"engines": {
"node": ">= 12"
}
},
"src/brave-search/node_modules/node-fetch": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
"integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
"license": "MIT",
"dependencies": {
"data-uri-to-buffer": "^4.0.0",
"fetch-blob": "^3.1.4",
"formdata-polyfill": "^4.0.10"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/node-fetch"
}
},
"src/duckduckgo": {
"name": "@modelcontextprotocol/server-duckduckgo",
"version": "0.1.0",

View File

@@ -1,6 +1,6 @@
# Brave Search MCP Server
An MCP server implementation that integrates the Brave Search API, providing both web and local search capabilities through the Model Context Protocol.
An MCP server implementation that integrates the Brave Search API, providing both web and local search capabilities.
## Features
@@ -9,86 +9,38 @@ An MCP server implementation that integrates the Brave Search API, providing bot
- **Flexible Filtering**: Control result types, safety levels, and content freshness
- **Smart Fallbacks**: Local search automatically falls back to web when no results are found
## Tools
- **brave_web_search**
- Execute web searches with pagination and filtering
- Inputs:
- `query` (string): Search terms
- `count` (number, optional): Results per page (max 20)
- `offset` (number, optional): Pagination offset (max 9)
- **brave_local_search**
- Search for local businesses and services
- Inputs:
- `query` (string): Local search terms
- `count` (number, optional): Number of results (max 20)
- Automatically falls back to web search if no local results found
## Configuration
### Client Configuration
Add this to your MCP client config:
### Getting an API Key
1. Sign up for a [Brave Search API account](https://brave.com/search/api/)
2. Choose a plan (Free tier available with 2,000 queries/month)
3. Generate your API key [from the developer dashboard](https://api.search.brave.com/app/keys)
### Usage with Claude Desktop
Add this to your `claude_desktop_config.json`:
```json
"brave-search": {
"mcp-server-brave-search": {
"command": "mcp-server-brave-search",
"env": {
"BRAVE_API_KEY": "YOUR_API_KEY_HERE"
}
}
```
Alternatively, you can set the API key as an environment variable:
```bash
export BRAVE_API_KEY='your_actual_api_key_here'
```
### Getting an API Key
1. Sign up for a Brave Search API account
2. Choose a plan (Free tier available)
3. Generate your API key from the developer dashboard
## Tools
### brave_web_search
Performs general web searches:
```javascript
{
"name": "brave_web_search",
"arguments": {
"query": "latest AI developments",
"count": 10,
"freshness": "pw", // Past week
"safesearch": "moderate"
}
}
```
### brave_local_search
Finds local businesses and services:
```javascript
{
"name": "brave_local_search",
"arguments": {
"query": "pizza near Central Park",
"count": 5,
"units": "imperial"
}
}
```
## Key Implementation Details
- Rate limiting to respect API quotas (1 request/second, 15000/month)
- Parallel fetching of POI details and descriptions for local search
- Type-safe argument validation
- Comprehensive error handling and logging
## Development
```bash
# Install dependencies
npm install
# Build the server
npm run build
# Run the server
mcp-server-brave-search
```
## Contributing
Contributions welcome! Please check the issues tab or submit a PR.
## License
MIT - see [LICENSE](LICENSE) file for details.

View File

@@ -4,26 +4,18 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListResourcesRequestSchema,
ListToolsRequestSchema,
ReadResourceRequestSchema,
Tool,
} from "@modelcontextprotocol/sdk/types.js";
import fetch from "node-fetch";
// Define tool schemas
const WEB_SEARCH_TOOL: Tool = {
name: "brave_web_search",
description:
"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. " +
"Maximum 20 results per request, with offset for pagination. " +
"Additional features:\n" +
"- Safesearch: moderate (default), strict, or off\n" +
"- Freshness: filter by recency (past day/week/month/year)\n" +
"- Result types: web, news, videos, discussions\n" +
"- Spell check and query alteration support",
"Maximum 20 results per request, with offset for pagination. ",
inputSchema: {
type: "object",
properties: {
@@ -41,37 +33,6 @@ const WEB_SEARCH_TOOL: Tool = {
description: "Pagination offset (max 9, default 0)",
default: 0
},
freshness: {
type: "string",
description: "Filter by recency: pd (past day), pw (past week), pm (past month), or custom date range",
enum: ["pd", "pw", "pm", "py"]
},
safesearch: {
type: "string",
description: "Content filtering level",
enum: ["off", "moderate", "strict"],
default: "moderate"
},
country: {
type: "string",
description: "2-letter country code for localized results",
default: "US"
},
search_lang: {
type: "string",
description: "Search language (2+ char code)",
default: "en"
},
ui_lang: {
type: "string",
description: "UI language preference",
default: "en-US"
},
result_filter: {
type: "string",
description: "Comma-separated result types: web, news, videos, discussions, locations",
default: null
}
},
required: ["query"],
},
@@ -86,7 +47,6 @@ const LOCAL_SEARCH_TOOL: Tool = {
"- Business names and addresses\n" +
"- Ratings and review counts\n" +
"- Phone numbers and opening hours\n" +
"- AI-generated descriptions\n" +
"Use this when the query implies 'near me' or mentions specific locations. " +
"Automatically falls back to web search if no local results are found.",
inputSchema: {
@@ -101,26 +61,6 @@ const LOCAL_SEARCH_TOOL: Tool = {
description: "Number of results (1-20, default 5)",
default: 5
},
units: {
type: "string",
description: "Measurement system for distances",
enum: ["metric", "imperial"]
},
country: {
type: "string",
description: "2-letter country code for localized results",
default: "US"
},
search_lang: {
type: "string",
description: "Search language (2+ char code)",
default: "en"
},
ui_lang: {
type: "string",
description: "UI language preference",
default: "en-US"
}
},
required: ["query"]
}
@@ -135,7 +75,6 @@ const server = new Server(
{
capabilities: {
tools: {},
resources: {},
},
},
);
@@ -221,7 +160,6 @@ interface BraveDescription {
descriptions: {[id: string]: string};
}
// Type guard functions for arguments
function isBraveWebSearchArgs(args: unknown): args is { query: string; count?: number } {
return (
typeof args === "object" &&
@@ -240,19 +178,12 @@ function isBraveLocalSearchArgs(args: unknown): args is { query: string; count?:
);
}
// API functions
async function performWebSearch(query: string, count: number = 10, offset: number = 0) {
checkRateLimit();
const url = new URL('https://api.search.brave.com/res/v1/web/search');
url.searchParams.set('q', query);
url.searchParams.set('search_lang', 'en');
url.searchParams.set('count', Math.min(count, 20).toString()); // API limit
url.searchParams.set('offset', offset.toString());
url.searchParams.set('result_filter', 'web');
url.searchParams.set('text_decorations', '0');
url.searchParams.set('spellcheck', '0');
url.searchParams.set('safesearch', 'moderate');
url.searchParams.set('freshness', 'pw'); // Past week results
const response = await fetch(url, {
headers: {
@@ -377,32 +308,6 @@ Description: ${descData.descriptions[poi.id] || 'No description available'}
}).join('\n---\n') || 'No local results found';
}
// Resource handlers
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
resources: [
{
uri: "brave://search",
mimeType: "text/plain",
name: "Brave Search Interface",
},
],
}));
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
if (request.params.uri.toString() === "brave://search") {
return {
contents: [
{
uri: "brave://search",
mimeType: "text/plain",
text: "Brave Search API interface",
},
],
};
}
throw new Error("Resource not found");
});
// Tool handlers
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [WEB_SEARCH_TOOL, LOCAL_SEARCH_TOOL],