@dyingc/brave-search-mcp-server 2.0.69
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +356 -0
- package/dist/BraveAPI/index.js +162 -0
- package/dist/BraveAPI/types.js +46 -0
- package/dist/config.js +183 -0
- package/dist/constants.js +21 -0
- package/dist/helpers.js +10 -0
- package/dist/index.js +23 -0
- package/dist/protocols/http.js +81 -0
- package/dist/protocols/index.js +2 -0
- package/dist/protocols/stdio.js +11 -0
- package/dist/server.js +28 -0
- package/dist/tools/images/index.js +61 -0
- package/dist/tools/images/schemas/input.js +37 -0
- package/dist/tools/images/schemas/output.js +20 -0
- package/dist/tools/images/schemas/response.js +54 -0
- package/dist/tools/images/types.js +1 -0
- package/dist/tools/index.js +14 -0
- package/dist/tools/local/index.js +145 -0
- package/dist/tools/local/params.js +7 -0
- package/dist/tools/local/types.js +1 -0
- package/dist/tools/news/index.js +62 -0
- package/dist/tools/news/params.js +62 -0
- package/dist/tools/news/types.js +1 -0
- package/dist/tools/summarizer/index.js +96 -0
- package/dist/tools/summarizer/params.js +60 -0
- package/dist/tools/summarizer/types.js +1 -0
- package/dist/tools/videos/index.js +46 -0
- package/dist/tools/videos/params.js +53 -0
- package/dist/tools/videos/types.js +1 -0
- package/dist/tools/web/index.js +149 -0
- package/dist/tools/web/params.js +217 -0
- package/dist/tools/web/types.js +1 -0
- package/dist/utils/apiKeyManager.js +124 -0
- package/dist/utils/retry.js +152 -0
- package/dist/utils/retry.test.js +258 -0
- package/dist/utils.js +21 -0
- package/package.json +66 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Anthropic, PBC
|
|
4
|
+
Copyright (c) 2025 Brave Software, Inc
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
# Brave Search MCP Server
|
|
2
|
+
|
|
3
|
+
An MCP server implementation that integrates the Brave Search API, providing comprehensive search capabilities including web search, local business search, image search, video search, news search, and AI-powered summarization. This project supports both STDIO and HTTP transports, with STDIO as the default mode.
|
|
4
|
+
|
|
5
|
+
[](https://deepwiki.com/brave/brave-search-mcp-server)
|
|
6
|
+
|
|
7
|
+
## Migration
|
|
8
|
+
|
|
9
|
+
### 1.x to 2.x
|
|
10
|
+
|
|
11
|
+
#### Default transport now STDIO
|
|
12
|
+
|
|
13
|
+
To follow established MCP conventions, the server now defaults to STDIO. If you would like to continue using HTTP, you will need to set the `BRAVE_MCP_TRANSPORT` environment variable to `http`, or provide the runtime argument `--transport http` when launching the server.
|
|
14
|
+
|
|
15
|
+
#### Response structure of `brave_image_search`
|
|
16
|
+
|
|
17
|
+
Version 1.x of the MCP server would return base64-encoded image data along with image URLs. This dramatically slowed down the response, as well as consumed unnecessarily context in the session. Version 2.x removes the base64-encoded data, and returns a response object that more closely reflects the original Brave Search API response. The updated output schema is defined in [`src/tools/images/schemas/output.ts`](https://github.com/brave/brave-search-mcp-server/blob/main/src/tools/images/schemas/output.ts).
|
|
18
|
+
|
|
19
|
+
## Tools
|
|
20
|
+
|
|
21
|
+
### Web Search (`brave_web_search`)
|
|
22
|
+
Performs comprehensive web searches with rich result types and advanced filtering options.
|
|
23
|
+
|
|
24
|
+
**Parameters:**
|
|
25
|
+
- `query` (string, required): Search terms (max 400 chars, 50 words)
|
|
26
|
+
- `country` (string, optional): Country code (default: "US")
|
|
27
|
+
- `search_lang` (string, optional): Search language (default: "en")
|
|
28
|
+
- `ui_lang` (string, optional): UI language (default: "en-US")
|
|
29
|
+
- `count` (number, optional): Results per page (1-20, default: 10)
|
|
30
|
+
- `offset` (number, optional): Pagination offset (max 9, default: 0)
|
|
31
|
+
- `safesearch` (string, optional): Content filtering ("off", "moderate", "strict", default: "moderate")
|
|
32
|
+
- `freshness` (string, optional): Time filter ("pd", "pw", "pm", "py", or date range)
|
|
33
|
+
- `text_decorations` (boolean, optional): Include highlighting markers (default: true)
|
|
34
|
+
- `spellcheck` (boolean, optional): Enable spell checking (default: true)
|
|
35
|
+
- `result_filter` (array, optional): Filter result types (default: ["web", "query"])
|
|
36
|
+
- `goggles` (array, optional): Custom re-ranking definitions
|
|
37
|
+
- `units` (string, optional): Measurement units ("metric" or "imperial")
|
|
38
|
+
- `extra_snippets` (boolean, optional): Get additional excerpts (Pro plans only)
|
|
39
|
+
- `summary` (boolean, optional): Enable summary key generation for AI summarization
|
|
40
|
+
|
|
41
|
+
### Local Search (`brave_local_search`)
|
|
42
|
+
Searches for local businesses and places with detailed information including ratings, hours, and AI-generated descriptions.
|
|
43
|
+
|
|
44
|
+
**Parameters:**
|
|
45
|
+
- Same as `brave_web_search` with automatic location filtering
|
|
46
|
+
- Automatically includes "web" and "locations" in result_filter
|
|
47
|
+
|
|
48
|
+
**Note:** Requires Pro plan for full local search capabilities. Falls back to web search otherwise.
|
|
49
|
+
|
|
50
|
+
### Video Search (`brave_video_search`)
|
|
51
|
+
Searches for videos with comprehensive metadata and thumbnail information.
|
|
52
|
+
|
|
53
|
+
**Parameters:**
|
|
54
|
+
- `query` (string, required): Search terms (max 400 chars, 50 words)
|
|
55
|
+
- `country` (string, optional): Country code (default: "US")
|
|
56
|
+
- `search_lang` (string, optional): Search language (default: "en")
|
|
57
|
+
- `ui_lang` (string, optional): UI language (default: "en-US")
|
|
58
|
+
- `count` (number, optional): Results per page (1-50, default: 20)
|
|
59
|
+
- `offset` (number, optional): Pagination offset (max 9, default: 0)
|
|
60
|
+
- `spellcheck` (boolean, optional): Enable spell checking (default: true)
|
|
61
|
+
- `safesearch` (string, optional): Content filtering ("off", "moderate", "strict", default: "moderate")
|
|
62
|
+
- `freshness` (string, optional): Time filter ("pd", "pw", "pm", "py", or date range)
|
|
63
|
+
|
|
64
|
+
### Image Search (`brave_image_search`)
|
|
65
|
+
Searches for images with automatic fetching and base64 encoding for direct display.
|
|
66
|
+
|
|
67
|
+
**Parameters:**
|
|
68
|
+
- `query` (string, required): Search terms (max 400 chars, 50 words)
|
|
69
|
+
- `country` (string, optional): Country code (default: "US")
|
|
70
|
+
- `search_lang` (string, optional): Search language (default: "en")
|
|
71
|
+
- `count` (number, optional): Results per page (1-200, default: 50)
|
|
72
|
+
- `safesearch` (string, optional): Content filtering ("off", "strict", default: "strict")
|
|
73
|
+
- `spellcheck` (boolean, optional): Enable spell checking (default: true)
|
|
74
|
+
|
|
75
|
+
### News Search (`brave_news_search`)
|
|
76
|
+
Searches for current news articles with freshness controls and breaking news indicators.
|
|
77
|
+
|
|
78
|
+
**Parameters:**
|
|
79
|
+
- `query` (string, required): Search terms (max 400 chars, 50 words)
|
|
80
|
+
- `country` (string, optional): Country code (default: "US")
|
|
81
|
+
- `search_lang` (string, optional): Search language (default: "en")
|
|
82
|
+
- `ui_lang` (string, optional): UI language (default: "en-US")
|
|
83
|
+
- `count` (number, optional): Results per page (1-50, default: 20)
|
|
84
|
+
- `offset` (number, optional): Pagination offset (max 9, default: 0)
|
|
85
|
+
- `spellcheck` (boolean, optional): Enable spell checking (default: true)
|
|
86
|
+
- `safesearch` (string, optional): Content filtering ("off", "moderate", "strict", default: "moderate")
|
|
87
|
+
- `freshness` (string, optional): Time filter (default: "pd" for last 24 hours)
|
|
88
|
+
- `extra_snippets` (boolean, optional): Get additional excerpts (Pro plans only)
|
|
89
|
+
- `goggles` (array, optional): Custom re-ranking definitions
|
|
90
|
+
|
|
91
|
+
### Summarizer Search (`brave_summarizer`)
|
|
92
|
+
Generates AI-powered summaries from web search results using Brave's summarization API.
|
|
93
|
+
|
|
94
|
+
**Parameters:**
|
|
95
|
+
- `key` (string, required): Summary key from web search results (use `summary: true` in web search)
|
|
96
|
+
- `entity_info` (boolean, optional): Include entity information (default: false)
|
|
97
|
+
- `inline_references` (boolean, optional): Add source URL references (default: false)
|
|
98
|
+
|
|
99
|
+
**Usage:** First perform a web search with `summary: true`, then use the returned summary key with this tool.
|
|
100
|
+
|
|
101
|
+
## Configuration
|
|
102
|
+
|
|
103
|
+
### Getting an API Key
|
|
104
|
+
|
|
105
|
+
1. Sign up for a [Brave Search API account](https://brave.com/search/api/)
|
|
106
|
+
2. Choose a plan:
|
|
107
|
+
- **Free**: 2,000 queries/month, basic web search
|
|
108
|
+
- **Pro**: Enhanced features including local search, AI summaries, extra snippets
|
|
109
|
+
3. Generate your API key from the [developer dashboard](https://api-dashboard.search.brave.com/app/keys)
|
|
110
|
+
|
|
111
|
+
### Environment Variables
|
|
112
|
+
|
|
113
|
+
The server supports the following environment variables:
|
|
114
|
+
|
|
115
|
+
**Required:**
|
|
116
|
+
- `BRAVE_API_KEY`: Your Brave Search API key (required)
|
|
117
|
+
|
|
118
|
+
**Optional:**
|
|
119
|
+
- `BRAVE_MCP_TRANSPORT`: Transport mode ("http" or "stdio", default: "stdio")
|
|
120
|
+
- `BRAVE_MCP_PORT`: HTTP server port (default: 8000)
|
|
121
|
+
- `BRAVE_MCP_HOST`: HTTP server host (default: "0.0.0.0")
|
|
122
|
+
- `BRAVE_MCP_LOG_LEVEL`: Desired logging level("debug", "info", "notice", "warning", "error", "critical", "alert", or "emergency", default: "info")
|
|
123
|
+
- `BRAVE_MCP_ENABLED_TOOLS`: When used, specifies a whitelist for supported tools
|
|
124
|
+
- `BRAVE_MCP_DISABLED_TOOLS`: When used, specifies a blacklist for supported tools
|
|
125
|
+
- `BRAVE_MCP_STATELESS`: HTTP stateless mode (default: "false")
|
|
126
|
+
|
|
127
|
+
**Retry Configuration:**
|
|
128
|
+
- `BRAVE_MCP_MAX_RETRIES`: Maximum number of retry attempts for rate-limited requests (default: 5, range: 0-10)
|
|
129
|
+
- `BRAVE_MCP_RETRY_BASE_DELAY`: Base delay for retry in milliseconds (default: 1000, range: 100-60000)
|
|
130
|
+
- `BRAVE_MCP_RETRY_MAX_DELAY`: Maximum delay for retry in milliseconds (default: 30000, range: 1000-300000)
|
|
131
|
+
|
|
132
|
+
### Command Line Options
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
node dist/index.js [options]
|
|
136
|
+
|
|
137
|
+
Options:
|
|
138
|
+
--brave-api-key <string> Brave API key (required)
|
|
139
|
+
--transport <stdio|http> Transport type (default: stdio)
|
|
140
|
+
--port <number> HTTP server port (default: 8080)
|
|
141
|
+
--host <string> HTTP server host (default: 0.0.0.0)
|
|
142
|
+
--logging-level <string> Desired logging level (one of _debug_, _info_, _notice_, _warning_, _error_, _critical_, _alert_, or _emergency_)
|
|
143
|
+
--enabled-tools Tools whitelist (only the specified tools will be enabled)
|
|
144
|
+
--disabled-tools Tools blacklist (included tools will be disabled)
|
|
145
|
+
--stateless <boolean> HTTP Stateless flag
|
|
146
|
+
--retry-max-attempts <number> Maximum retry attempts (default: 5, range: 0-10)
|
|
147
|
+
--retry-base-delay <number> Base delay in milliseconds (default: 1000, range: 100-60000)
|
|
148
|
+
--retry-max-delay <number> Maximum delay in milliseconds (default: 30000, range: 1000-300000)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Installation
|
|
152
|
+
|
|
153
|
+
### Installing via Smithery
|
|
154
|
+
|
|
155
|
+
To install Brave Search automatically via [Smithery](https://smithery.ai/server/brave):
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
npx -y @smithery/cli install brave
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Usage with Claude Desktop
|
|
162
|
+
|
|
163
|
+
Add this to your `claude_desktop_config.json`:
|
|
164
|
+
|
|
165
|
+
#### Docker
|
|
166
|
+
|
|
167
|
+
```json
|
|
168
|
+
{
|
|
169
|
+
"mcpServers": {
|
|
170
|
+
"brave-search": {
|
|
171
|
+
"command": "docker",
|
|
172
|
+
"args": ["run", "-i", "--rm", "-e", "BRAVE_API_KEY", "docker.io/mcp/brave-search"],
|
|
173
|
+
"env": {
|
|
174
|
+
"BRAVE_API_KEY": "YOUR_API_KEY_HERE"
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
#### NPX
|
|
182
|
+
|
|
183
|
+
```json
|
|
184
|
+
{
|
|
185
|
+
"mcpServers": {
|
|
186
|
+
"brave-search": {
|
|
187
|
+
"command": "npx",
|
|
188
|
+
"args": ["-y", "@brave/brave-search-mcp-server", "--transport", "http"],
|
|
189
|
+
"env": {
|
|
190
|
+
"BRAVE_API_KEY": "YOUR_API_KEY_HERE"
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Usage with VS Code
|
|
198
|
+
|
|
199
|
+
For quick installation, use the one-click installation buttons below:
|
|
200
|
+
|
|
201
|
+
[](https://insiders.vscode.dev/redirect/mcp/install?name=brave-search&inputs=%5B%7B%22password%22%3Atrue%2C%22id%22%3A%22brave-api-key%22%2C%22type%22%3A%22promptString%22%2C%22description%22%3A%22Brave+Search+API+Key%22%7D%5D&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40brave%2Fbrave-search-mcp-server%22%2C%22--transport%22%2C%22stdio%22%5D%2C%22env%22%3A%7B%22BRAVE_API_KEY%22%3A%22%24%7Binput%3Abrave-api-key%7D%22%7D%7D) [](https://insiders.vscode.dev/redirect/mcp/install?name=brave-search&inputs=%5B%7B%22password%22%3Atrue%2C%22id%22%3A%22brave-api-key%22%2C%22type%22%3A%22promptString%22%2C%22description%22%3A%22Brave+Search+API+Key%22%7D%5D&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40brave%2Fbrave-search-mcp-server%22%2C%22--transport%22%2C%22stdio%22%5D%2C%22env%22%3A%7B%22BRAVE_API_KEY%22%3A%22%24%7Binput%3Abrave-api-key%7D%22%7D%7D&quality=insiders)
|
|
202
|
+
[](https://insiders.vscode.dev/redirect/mcp/install?name=brave-search&inputs=%5B%7B%22password%22%3Atrue%2C%22id%22%3A%22brave-api-key%22%2C%22type%22%3A%22promptString%22%2C%22description%22%3A%22Brave+Search+API+Key%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) [](https://insiders.vscode.dev/redirect/mcp/install?name=brave-search&inputs=%5B%7B%22password%22%3Atrue%2C%22id%22%3A%22brave-api-key%22%2C%22type%22%3A%22promptString%22%2C%22description%22%3A%22Brave+Search+API+Key%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)
|
|
203
|
+
|
|
204
|
+
For manual installation, add the following to your User Settings (JSON) or `.vscode/mcp.json`:
|
|
205
|
+
|
|
206
|
+
#### Docker
|
|
207
|
+
|
|
208
|
+
```json
|
|
209
|
+
{
|
|
210
|
+
"inputs": [
|
|
211
|
+
{
|
|
212
|
+
"password": true,
|
|
213
|
+
"id": "brave-api-key",
|
|
214
|
+
"type": "promptString",
|
|
215
|
+
"description": "Brave Search API Key",
|
|
216
|
+
}
|
|
217
|
+
],
|
|
218
|
+
"servers": {
|
|
219
|
+
"brave-search": {
|
|
220
|
+
"command": "docker",
|
|
221
|
+
"args": ["run", "-i", "--rm", "-e", "BRAVE_API_KEY", "mcp/brave-search"],
|
|
222
|
+
"env": {
|
|
223
|
+
"BRAVE_API_KEY": "${input:brave-api-key}"
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
#### NPX
|
|
231
|
+
|
|
232
|
+
```json
|
|
233
|
+
{
|
|
234
|
+
"inputs": [
|
|
235
|
+
{
|
|
236
|
+
"password": true,
|
|
237
|
+
"id": "brave-api-key",
|
|
238
|
+
"type": "promptString",
|
|
239
|
+
"description": "Brave Search API Key",
|
|
240
|
+
}
|
|
241
|
+
],
|
|
242
|
+
"servers": {
|
|
243
|
+
"brave-search-mcp-server": {
|
|
244
|
+
"command": "npx",
|
|
245
|
+
"args": ["-y", "@brave/brave-search-mcp-server", "--transport", "stdio"],
|
|
246
|
+
"env": {
|
|
247
|
+
"BRAVE_API_KEY": "${input:brave-api-key}"
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## Build
|
|
255
|
+
|
|
256
|
+
### Docker
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
docker build -t mcp/brave-search:latest .
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Local Build
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
npm install
|
|
266
|
+
npm run build
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## Development
|
|
270
|
+
|
|
271
|
+
### Prerequisites
|
|
272
|
+
|
|
273
|
+
- Node.js 22.x or higher
|
|
274
|
+
- npm
|
|
275
|
+
- Brave Search API key
|
|
276
|
+
|
|
277
|
+
### Setup
|
|
278
|
+
|
|
279
|
+
1. Clone the repository:
|
|
280
|
+
```bash
|
|
281
|
+
git clone https://github.com/brave/brave-search-mcp-server.git
|
|
282
|
+
cd brave-search-mcp-server
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
2. Install dependencies:
|
|
286
|
+
```bash
|
|
287
|
+
npm install
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
3. Build the project:
|
|
291
|
+
```bash
|
|
292
|
+
npm run build
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Testing via Claude Desktop
|
|
296
|
+
|
|
297
|
+
Add a reference to your local build in `claude_desktop_config.json`:
|
|
298
|
+
|
|
299
|
+
```json
|
|
300
|
+
{
|
|
301
|
+
"mcpServers": {
|
|
302
|
+
"brave-search-dev": {
|
|
303
|
+
"command": "node",
|
|
304
|
+
"args": ["C:\\GitHub\\brave-search-mcp-server\\dist\\index.js"], // Verify your path
|
|
305
|
+
"env": {
|
|
306
|
+
"BRAVE_API_KEY": "YOUR_API_KEY_HERE"
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### Testing via MCP Inspector
|
|
314
|
+
|
|
315
|
+
1. Build and start the server:
|
|
316
|
+
```bash
|
|
317
|
+
npm run build
|
|
318
|
+
node dist/index.js
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
2. In another terminal, start the MCP Inspector:
|
|
322
|
+
```bash
|
|
323
|
+
npx @modelcontextprotocol/inspector node dist/index.js
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
STDIO is the default mode. For HTTP mode testing, add `--transport http` to the arguments in the Inspector UI.
|
|
327
|
+
|
|
328
|
+
### Testing via Smithery.AI
|
|
329
|
+
|
|
330
|
+
1. Establish and acquire a smithery.ai account and API key
|
|
331
|
+
2. Run `npm run install`, `npm run smithery:build`, and lastly `npm run smithery:dev` to begin testing
|
|
332
|
+
|
|
333
|
+
### Available Scripts
|
|
334
|
+
|
|
335
|
+
- `npm run build`: Build the TypeScript project
|
|
336
|
+
- `npm run watch`: Watch for changes and rebuild
|
|
337
|
+
- `npm run format`: Format code with Prettier
|
|
338
|
+
- `npm run format:check`: Check code formatting
|
|
339
|
+
- `npm run prepare`: Format and build (runs automatically on npm install)
|
|
340
|
+
|
|
341
|
+
- `npm run inspector`: Launch an instance of MCP Inspector
|
|
342
|
+
- `npm run inspector:stdio`: Launch a instance of MCP Inspector, configured for STDIO
|
|
343
|
+
- `npm run smithery:build`: Build the project for smithery.ai
|
|
344
|
+
- `npm run smithery:dev`: Launch the development environment for smithery.ai
|
|
345
|
+
|
|
346
|
+
### Docker Compose
|
|
347
|
+
|
|
348
|
+
For local development with Docker:
|
|
349
|
+
|
|
350
|
+
```bash
|
|
351
|
+
docker-compose up --build
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
## License
|
|
355
|
+
|
|
356
|
+
This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository.
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import config from '../config.js';
|
|
2
|
+
import { withRetry, BraveApiError } from '../utils/retry.js';
|
|
3
|
+
import { extractRetryAfterMs } from './types.js';
|
|
4
|
+
import { getApiKeyManager } from '../utils/apiKeyManager.js';
|
|
5
|
+
const typeToPathMap = {
|
|
6
|
+
images: '/res/v1/images/search',
|
|
7
|
+
localPois: '/res/v1/local/pois',
|
|
8
|
+
localDescriptions: '/res/v1/local/descriptions',
|
|
9
|
+
news: '/res/v1/news/search',
|
|
10
|
+
videos: '/res/v1/videos/search',
|
|
11
|
+
web: '/res/v1/web/search',
|
|
12
|
+
summarizer: '/res/v1/summarizer/search',
|
|
13
|
+
};
|
|
14
|
+
const getDefaultRequestHeaders = () => {
|
|
15
|
+
const manager = getApiKeyManager();
|
|
16
|
+
// Use key manager if available, otherwise fallback to first key
|
|
17
|
+
const keyToUse = manager ? manager.selectBestKey() : config.braveApiKeys[0];
|
|
18
|
+
if (!keyToUse) {
|
|
19
|
+
throw new Error('No valid API key available');
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
Accept: 'application/json',
|
|
23
|
+
'Accept-Encoding': 'gzip',
|
|
24
|
+
'X-Subscription-Token': keyToUse,
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
const isValidGoggleURL = (url) => {
|
|
28
|
+
try {
|
|
29
|
+
// Only allow HTTPS URLs
|
|
30
|
+
return new URL(url).protocol === 'https:';
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Perform the actual HTTP request
|
|
38
|
+
* This function is separated from issueRequest to allow retry logic
|
|
39
|
+
*
|
|
40
|
+
* @param urlWithParams - Full URL with query parameters
|
|
41
|
+
* @param headers - Request headers
|
|
42
|
+
* @returns Promise resolving to the response
|
|
43
|
+
* @throws BraveApiError on HTTP errors
|
|
44
|
+
*/
|
|
45
|
+
async function performRequest(urlWithParams, headers) {
|
|
46
|
+
const response = await fetch(urlWithParams, { headers });
|
|
47
|
+
// Extract used key for tracking
|
|
48
|
+
const usedApiKey = headers['X-Subscription-Token'];
|
|
49
|
+
const manager = getApiKeyManager();
|
|
50
|
+
// Handle Error
|
|
51
|
+
if (!response.ok) {
|
|
52
|
+
let responseBody;
|
|
53
|
+
try {
|
|
54
|
+
responseBody = await response.json();
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// If JSON parsing fails, try to get text
|
|
58
|
+
const text = await response.text();
|
|
59
|
+
responseBody = { message: text };
|
|
60
|
+
}
|
|
61
|
+
// Update key state on error
|
|
62
|
+
if (manager && usedApiKey) {
|
|
63
|
+
if (response.status === 401) {
|
|
64
|
+
manager.markKeyInvalid(usedApiKey, responseBody.message || 'Unauthorized');
|
|
65
|
+
}
|
|
66
|
+
else if (response.status === 429) {
|
|
67
|
+
const retryAfter = extractRetryAfterMs(responseBody);
|
|
68
|
+
manager.markKeyRateLimited(usedApiKey, retryAfter);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
// Release key for other errors
|
|
72
|
+
manager.releaseKey(usedApiKey);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Extract retry-after from rate limit metadata if available
|
|
76
|
+
const retryAfter = extractRetryAfterMs(responseBody);
|
|
77
|
+
// Throw structured error for retry logic to handle
|
|
78
|
+
throw new BraveApiError(response.status, responseBody, retryAfter);
|
|
79
|
+
}
|
|
80
|
+
// Update key state from response headers on success
|
|
81
|
+
if (manager && usedApiKey) {
|
|
82
|
+
manager.updateKeyFromHeaders(usedApiKey, response.headers);
|
|
83
|
+
}
|
|
84
|
+
// Return Response
|
|
85
|
+
const responseBody = await response.json();
|
|
86
|
+
return responseBody;
|
|
87
|
+
}
|
|
88
|
+
async function issueRequest(endpoint, parameters,
|
|
89
|
+
// TODO (Sampson): Implement support for custom request headers (helpful for POIs, etc.)
|
|
90
|
+
requestHeaders = {}) {
|
|
91
|
+
// Determine URL, and setup parameters
|
|
92
|
+
const url = new URL(`https://api.search.brave.com${typeToPathMap[endpoint]}`);
|
|
93
|
+
const queryParams = new URLSearchParams();
|
|
94
|
+
// TODO (Sampson): Move param-construction/validation to modules
|
|
95
|
+
for (const [key, value] of Object.entries(parameters)) {
|
|
96
|
+
// The 'ids' parameter is expected to appear multiple times for multiple IDs
|
|
97
|
+
if (['localPois', 'localDescriptions'].includes(endpoint)) {
|
|
98
|
+
if (key === 'ids') {
|
|
99
|
+
if (Array.isArray(value) && value.length > 0) {
|
|
100
|
+
value.forEach((id) => queryParams.append(key, id));
|
|
101
|
+
}
|
|
102
|
+
else if (typeof value === 'string') {
|
|
103
|
+
queryParams.set(key, value);
|
|
104
|
+
}
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Handle `result_filter` parameter
|
|
109
|
+
if (key === 'result_filter') {
|
|
110
|
+
// Handle special behavior of 'summary' parameter:
|
|
111
|
+
// Requires `result_filter` to be empty, or only contain 'summarizer'
|
|
112
|
+
// see: https://bravesoftware.slack.com/archives/C01NNFM9XMM/p1751654841090929
|
|
113
|
+
if ('summary' in parameters && parameters.summary === true) {
|
|
114
|
+
queryParams.set(key, 'summarizer');
|
|
115
|
+
}
|
|
116
|
+
else if (Array.isArray(value) && value.length > 0) {
|
|
117
|
+
queryParams.set(key, value.join(','));
|
|
118
|
+
}
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
// Handle `goggles` parameter(s)
|
|
122
|
+
if (key === 'goggles') {
|
|
123
|
+
if (typeof value === 'string') {
|
|
124
|
+
queryParams.set(key, value);
|
|
125
|
+
}
|
|
126
|
+
else if (Array.isArray(value)) {
|
|
127
|
+
for (const url of value.filter(isValidGoggleURL)) {
|
|
128
|
+
queryParams.append(key, url);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (value !== undefined) {
|
|
134
|
+
queryParams.set(key === 'query' ? 'q' : key, value.toString());
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// Build final URL and headers
|
|
138
|
+
const urlWithParams = url.toString() + '?' + queryParams.toString();
|
|
139
|
+
const headers = { ...getDefaultRequestHeaders(), ...requestHeaders };
|
|
140
|
+
// Special case: Summarizer uses its own polling logic, don't apply retry
|
|
141
|
+
if (endpoint === 'summarizer') {
|
|
142
|
+
return performRequest(urlWithParams, headers);
|
|
143
|
+
}
|
|
144
|
+
// All other endpoints: Use retry logic with exponential backoff
|
|
145
|
+
return withRetry(() => performRequest(urlWithParams, headers), {
|
|
146
|
+
maxRetries: config.retryMaxAttempts,
|
|
147
|
+
baseDelay: config.retryBaseDelay,
|
|
148
|
+
maxDelay: config.retryMaxDelay,
|
|
149
|
+
onRetry: (attempt, error, delay) => {
|
|
150
|
+
// Additional logging can be added here if needed
|
|
151
|
+
if (error instanceof BraveApiError && error.statusCode === 429) {
|
|
152
|
+
// Log specific rate limit info if available
|
|
153
|
+
if (error.retryAfter) {
|
|
154
|
+
console.info(`Rate limit metadata suggests ${error.retryAfter}ms delay`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
export default {
|
|
161
|
+
issueRequest,
|
|
162
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type guard to check if an error response is a rate limit error
|
|
3
|
+
* @param error - The error to check
|
|
4
|
+
* @returns True if the error is a rate limit error
|
|
5
|
+
*/
|
|
6
|
+
export function isRateLimitError(error) {
|
|
7
|
+
return (typeof error === 'object' &&
|
|
8
|
+
error !== null &&
|
|
9
|
+
'type' in error &&
|
|
10
|
+
error.type === 'ErrorResponse' &&
|
|
11
|
+
'error' in error &&
|
|
12
|
+
error.error?.code === 'RATE_LIMITED');
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Extract the suggested retry delay from a rate limit error response
|
|
16
|
+
* Uses the rate limit metadata to calculate the optimal delay
|
|
17
|
+
* @param response - The error response from the API
|
|
18
|
+
* @returns Suggested delay in milliseconds, or undefined if no metadata available
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* const response = {
|
|
23
|
+
* error: {
|
|
24
|
+
* meta: { rate_limit: 1, rate_current: 1.5 }
|
|
25
|
+
* }
|
|
26
|
+
* };
|
|
27
|
+
* const delay = extractRetryAfterMs(response); // Returns 1500
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export function extractRetryAfterMs(response) {
|
|
31
|
+
if (!response || typeof response !== 'object') {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
const meta = response?.error?.meta;
|
|
35
|
+
if (!meta || typeof meta !== 'object') {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
const rateLimit = meta.rate_limit;
|
|
39
|
+
const rateCurrent = meta.rate_current;
|
|
40
|
+
if (typeof rateLimit === 'number' && typeof rateCurrent === 'number' && rateLimit > 0) {
|
|
41
|
+
// Calculate delay based on current usage rate
|
|
42
|
+
// Example: If rate_limit=1, rate_current=1.5 → 1500ms
|
|
43
|
+
return Math.ceil((rateCurrent / rateLimit) * 1000);
|
|
44
|
+
}
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|