@brave/brave-search-mcp-server 1.3.1
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 +305 -0
- package/dist/BraveAPI/index.js +94 -0
- package/dist/BraveAPI/types.js +1 -0
- package/dist/config.js +44 -0
- package/dist/constants.js +4 -0
- package/dist/helpers.js +10 -0
- package/dist/index.js +26 -0
- package/dist/protocols/http.js +41 -0
- package/dist/protocols/index.js +2 -0
- package/dist/protocols/stdio.js +8 -0
- package/dist/server.js +16 -0
- package/dist/tools/images/index.js +50 -0
- package/dist/tools/images/params.js +38 -0
- package/dist/tools/images/types.js +1 -0
- package/dist/tools/index.js +14 -0
- package/dist/tools/local/index.js +136 -0
- package/dist/tools/local/params.js +7 -0
- package/dist/tools/local/types.js +1 -0
- package/dist/tools/news/index.js +53 -0
- package/dist/tools/news/params.js +64 -0
- package/dist/tools/news/types.js +1 -0
- package/dist/tools/summarizer/index.js +89 -0
- package/dist/tools/summarizer/params.js +60 -0
- package/dist/tools/summarizer/types.js +1 -0
- package/dist/tools/videos/index.js +37 -0
- package/dist/tools/videos/params.js +55 -0
- package/dist/tools/videos/types.js +1 -0
- package/dist/tools/web/index.js +140 -0
- package/dist/tools/web/params.js +220 -0
- package/dist/tools/web/types.js +1 -0
- package/dist/utils.js +26 -0
- package/package.json +46 -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,305 @@
|
|
|
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 HTTP and STDIO transports, with HTTP as the default mode.
|
|
4
|
+
|
|
5
|
+
## Tools
|
|
6
|
+
|
|
7
|
+
### Web Search (`brave_web_search`)
|
|
8
|
+
Performs comprehensive web searches with rich result types and advanced filtering options.
|
|
9
|
+
|
|
10
|
+
**Parameters:**
|
|
11
|
+
- `query` (string, required): Search terms (max 400 chars, 50 words)
|
|
12
|
+
- `country` (string, optional): Country code (default: "US")
|
|
13
|
+
- `search_lang` (string, optional): Search language (default: "en")
|
|
14
|
+
- `ui_lang` (string, optional): UI language (default: "en-US")
|
|
15
|
+
- `count` (number, optional): Results per page (1-20, default: 10)
|
|
16
|
+
- `offset` (number, optional): Pagination offset (max 9, default: 0)
|
|
17
|
+
- `safesearch` (string, optional): Content filtering ("off", "moderate", "strict", default: "moderate")
|
|
18
|
+
- `freshness` (string, optional): Time filter ("pd", "pw", "pm", "py", or date range)
|
|
19
|
+
- `text_decorations` (boolean, optional): Include highlighting markers (default: true)
|
|
20
|
+
- `spellcheck` (boolean, optional): Enable spell checking (default: true)
|
|
21
|
+
- `result_filter` (array, optional): Filter result types (default: ["web", "query"])
|
|
22
|
+
- `goggles` (array, optional): Custom re-ranking definitions
|
|
23
|
+
- `units` (string, optional): Measurement units ("metric" or "imperial")
|
|
24
|
+
- `extra_snippets` (boolean, optional): Get additional excerpts (Pro plans only)
|
|
25
|
+
- `summary` (boolean, optional): Enable summary key generation for AI summarization
|
|
26
|
+
|
|
27
|
+
### Local Search (`brave_local_search`)
|
|
28
|
+
Searches for local businesses and places with detailed information including ratings, hours, and AI-generated descriptions.
|
|
29
|
+
|
|
30
|
+
**Parameters:**
|
|
31
|
+
- Same as `brave_web_search` with automatic location filtering
|
|
32
|
+
- Automatically includes "web" and "locations" in result_filter
|
|
33
|
+
|
|
34
|
+
**Note:** Requires Pro plan for full local search capabilities. Falls back to web search otherwise.
|
|
35
|
+
|
|
36
|
+
### Video Search (`brave_video_search`)
|
|
37
|
+
Searches for videos with comprehensive metadata and thumbnail information.
|
|
38
|
+
|
|
39
|
+
**Parameters:**
|
|
40
|
+
- `query` (string, required): Search terms (max 400 chars, 50 words)
|
|
41
|
+
- `country` (string, optional): Country code (default: "US")
|
|
42
|
+
- `search_lang` (string, optional): Search language (default: "en")
|
|
43
|
+
- `ui_lang` (string, optional): UI language (default: "en-US")
|
|
44
|
+
- `count` (number, optional): Results per page (1-50, default: 20)
|
|
45
|
+
- `offset` (number, optional): Pagination offset (max 9, default: 0)
|
|
46
|
+
- `spellcheck` (boolean, optional): Enable spell checking (default: true)
|
|
47
|
+
- `safesearch` (string, optional): Content filtering ("off", "moderate", "strict", default: "moderate")
|
|
48
|
+
- `freshness` (string, optional): Time filter ("pd", "pw", "pm", "py", or date range)
|
|
49
|
+
|
|
50
|
+
### Image Search (`brave_image_search`)
|
|
51
|
+
Searches for images with automatic fetching and base64 encoding for direct display.
|
|
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
|
+
- `count` (number, optional): Results per page (1-200, default: 50)
|
|
58
|
+
- `safesearch` (string, optional): Content filtering ("off", "strict", default: "strict")
|
|
59
|
+
- `spellcheck` (boolean, optional): Enable spell checking (default: true)
|
|
60
|
+
|
|
61
|
+
### News Search (`brave_news_search`)
|
|
62
|
+
Searches for current news articles with freshness controls and breaking news indicators.
|
|
63
|
+
|
|
64
|
+
**Parameters:**
|
|
65
|
+
- `query` (string, required): Search terms (max 400 chars, 50 words)
|
|
66
|
+
- `country` (string, optional): Country code (default: "US")
|
|
67
|
+
- `search_lang` (string, optional): Search language (default: "en")
|
|
68
|
+
- `ui_lang` (string, optional): UI language (default: "en-US")
|
|
69
|
+
- `count` (number, optional): Results per page (1-50, default: 20)
|
|
70
|
+
- `offset` (number, optional): Pagination offset (max 9, default: 0)
|
|
71
|
+
- `spellcheck` (boolean, optional): Enable spell checking (default: true)
|
|
72
|
+
- `safesearch` (string, optional): Content filtering ("off", "moderate", "strict", default: "moderate")
|
|
73
|
+
- `freshness` (string, optional): Time filter (default: "pd" for last 24 hours)
|
|
74
|
+
- `extra_snippets` (boolean, optional): Get additional excerpts (Pro plans only)
|
|
75
|
+
- `goggles` (array, optional): Custom re-ranking definitions
|
|
76
|
+
|
|
77
|
+
### Summarizer Search (`brave_summarizer`)
|
|
78
|
+
Generates AI-powered summaries from web search results using Brave's summarization API.
|
|
79
|
+
|
|
80
|
+
**Parameters:**
|
|
81
|
+
- `key` (string, required): Summary key from web search results (use `summary: true` in web search)
|
|
82
|
+
- `entity_info` (boolean, optional): Include entity information (default: false)
|
|
83
|
+
- `inline_references` (boolean, optional): Add source URL references (default: false)
|
|
84
|
+
|
|
85
|
+
**Usage:** First perform a web search with `summary: true`, then use the returned summary key with this tool.
|
|
86
|
+
|
|
87
|
+
## Configuration
|
|
88
|
+
|
|
89
|
+
### Getting an API Key
|
|
90
|
+
|
|
91
|
+
1. Sign up for a [Brave Search API account](https://brave.com/search/api/)
|
|
92
|
+
2. Choose a plan:
|
|
93
|
+
- **Free**: 2,000 queries/month, basic web search
|
|
94
|
+
- **Pro**: Enhanced features including local search, AI summaries, extra snippets
|
|
95
|
+
3. Generate your API key from the [developer dashboard](https://api-dashboard.search.brave.com/app/keys)
|
|
96
|
+
|
|
97
|
+
### Environment Variables
|
|
98
|
+
|
|
99
|
+
The server supports the following environment variables:
|
|
100
|
+
|
|
101
|
+
- `BRAVE_API_KEY`: Your Brave Search API key (required)
|
|
102
|
+
- `BRAVE_MCP_TRANSPORT`: Transport mode ("http" or "stdio", default: "http")
|
|
103
|
+
- `BRAVE_MCP_PORT`: HTTP server port (default: 8080)
|
|
104
|
+
- `BRAVE_MCP_HOST`: HTTP server host (default: "0.0.0.0")
|
|
105
|
+
|
|
106
|
+
### Command Line Options
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
node dist/index.js [options]
|
|
110
|
+
|
|
111
|
+
Options:
|
|
112
|
+
--brave-api-key <string> Brave API key
|
|
113
|
+
--transport <stdio|http> Transport type (default: http)
|
|
114
|
+
--port <number> HTTP server port (default: 8080)
|
|
115
|
+
--host <string> HTTP server host (default: 0.0.0.0)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Installation
|
|
119
|
+
|
|
120
|
+
### Usage with Claude Desktop
|
|
121
|
+
|
|
122
|
+
Add this to your `claude_desktop_config.json`:
|
|
123
|
+
|
|
124
|
+
#### Docker
|
|
125
|
+
|
|
126
|
+
```json
|
|
127
|
+
{
|
|
128
|
+
"mcpServers": {
|
|
129
|
+
"brave-search": {
|
|
130
|
+
"command": "docker",
|
|
131
|
+
"args": ["run", "-i", "--rm", "-e", "BRAVE_API_KEY", "mcp/brave-search"],
|
|
132
|
+
"env": {
|
|
133
|
+
"BRAVE_API_KEY": "YOUR_API_KEY_HERE"
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
#### NPX
|
|
141
|
+
|
|
142
|
+
```json
|
|
143
|
+
{
|
|
144
|
+
"mcpServers": {
|
|
145
|
+
"brave-search": {
|
|
146
|
+
"command": "npx",
|
|
147
|
+
"args": ["-y", "@brave/brave-search-mcp-server"],
|
|
148
|
+
"env": {
|
|
149
|
+
"BRAVE_API_KEY": "YOUR_API_KEY_HERE"
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Usage with VS Code
|
|
157
|
+
|
|
158
|
+
For quick installation, use the one-click installation buttons below:
|
|
159
|
+
|
|
160
|
+
[](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%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%5D%2C%22env%22%3A%7B%22BRAVE_API_KEY%22%3A%22%24%7Binput%3Abrave-api-key%7D%22%7D%7D&quality=insiders)
|
|
161
|
+
[](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)
|
|
162
|
+
|
|
163
|
+
For manual installation, add the following to your User Settings (JSON) or `.vscode/mcp.json`:
|
|
164
|
+
|
|
165
|
+
#### Docker
|
|
166
|
+
|
|
167
|
+
```json
|
|
168
|
+
{
|
|
169
|
+
"inputs": [
|
|
170
|
+
{
|
|
171
|
+
"password": true,
|
|
172
|
+
"id": "brave-api-key",
|
|
173
|
+
"type": "promptString",
|
|
174
|
+
"description": "Brave Search API Key",
|
|
175
|
+
}
|
|
176
|
+
],
|
|
177
|
+
"servers": {
|
|
178
|
+
"brave-search": {
|
|
179
|
+
"command": "docker",
|
|
180
|
+
"args": ["run", "-i", "--rm", "-e", "BRAVE_API_KEY", "mcp/brave-search"],
|
|
181
|
+
"env": {
|
|
182
|
+
"BRAVE_API_KEY": "${input:brave-api-key}"
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
#### NPX
|
|
190
|
+
|
|
191
|
+
```json
|
|
192
|
+
{
|
|
193
|
+
"inputs": [
|
|
194
|
+
{
|
|
195
|
+
"password": true,
|
|
196
|
+
"id": "brave-api-key",
|
|
197
|
+
"type": "promptString",
|
|
198
|
+
"description": "Brave Search API Key",
|
|
199
|
+
}
|
|
200
|
+
],
|
|
201
|
+
"servers": {
|
|
202
|
+
"brave-search-mcp-server": {
|
|
203
|
+
"command": "npx",
|
|
204
|
+
"args": ["-y", "@brave/brave-search-mcp-server"],
|
|
205
|
+
"env": {
|
|
206
|
+
"BRAVE_API_KEY": "${input:brave-api-key}"
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Build
|
|
214
|
+
|
|
215
|
+
### Docker
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
docker build -t mcp/brave-search:latest .
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Local Build
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
npm install
|
|
225
|
+
npm run build
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Development
|
|
229
|
+
|
|
230
|
+
### Prerequisites
|
|
231
|
+
|
|
232
|
+
- Node.js 22.x or higher
|
|
233
|
+
- npm
|
|
234
|
+
- Brave Search API key
|
|
235
|
+
|
|
236
|
+
### Setup
|
|
237
|
+
|
|
238
|
+
1. Clone the repository:
|
|
239
|
+
```bash
|
|
240
|
+
git clone https://github.com/brave/brave-search-mcp-server.git
|
|
241
|
+
cd brave-search-mcp-server
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
2. Install dependencies:
|
|
245
|
+
```bash
|
|
246
|
+
npm install
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
3. Build the project:
|
|
250
|
+
```bash
|
|
251
|
+
npm run build
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Testing via Claude Desktop
|
|
255
|
+
|
|
256
|
+
Add a reference to your local build in `claude_desktop_config.json`:
|
|
257
|
+
|
|
258
|
+
```json
|
|
259
|
+
{
|
|
260
|
+
"mcpServers": {
|
|
261
|
+
"brave-search-dev": {
|
|
262
|
+
"command": "node",
|
|
263
|
+
"args": ["C:\\GitHub\\brave-search-mcp-server\\dist\\index.js"], // Verify your path
|
|
264
|
+
"env": {
|
|
265
|
+
"BRAVE_API_KEY": "YOUR_API_KEY_HERE"
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### Testing via MCP Inspector
|
|
273
|
+
|
|
274
|
+
1. Build and start the server:
|
|
275
|
+
```bash
|
|
276
|
+
npm run build
|
|
277
|
+
node dist/index.js
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
2. In another terminal, start the MCP Inspector:
|
|
281
|
+
```bash
|
|
282
|
+
npx @modelcontextprotocol/inspector node dist/index.js
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
For STDIO mode testing, add `--transport stdio` to the arguments in the Inspector UI.
|
|
286
|
+
|
|
287
|
+
### Available Scripts
|
|
288
|
+
|
|
289
|
+
- `npm run build`: Build the TypeScript project
|
|
290
|
+
- `npm run watch`: Watch for changes and rebuild
|
|
291
|
+
- `npm run format`: Format code with Prettier
|
|
292
|
+
- `npm run format:check`: Check code formatting
|
|
293
|
+
- `npm run prepare`: Format and build (runs automatically on npm install)
|
|
294
|
+
|
|
295
|
+
### Docker Compose
|
|
296
|
+
|
|
297
|
+
For local development with Docker:
|
|
298
|
+
|
|
299
|
+
```bash
|
|
300
|
+
docker-compose up --build
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
## License
|
|
304
|
+
|
|
305
|
+
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,94 @@
|
|
|
1
|
+
import config from '../config.js';
|
|
2
|
+
import { log, stringify } from '../utils.js';
|
|
3
|
+
const typeToPathMap = {
|
|
4
|
+
images: '/res/v1/images/search',
|
|
5
|
+
localPois: '/res/v1/local/pois',
|
|
6
|
+
localDescriptions: '/res/v1/local/descriptions',
|
|
7
|
+
news: '/res/v1/news/search',
|
|
8
|
+
videos: '/res/v1/videos/search',
|
|
9
|
+
web: '/res/v1/web/search',
|
|
10
|
+
summarizer: '/res/v1/summarizer/search',
|
|
11
|
+
};
|
|
12
|
+
const defaultRequestHeaders = {
|
|
13
|
+
Accept: 'application/json',
|
|
14
|
+
'Accept-Encoding': 'gzip',
|
|
15
|
+
'X-Subscription-Token': config.braveApiKey,
|
|
16
|
+
};
|
|
17
|
+
async function issueRequest(endpoint, parameters,
|
|
18
|
+
// TODO (Sampson): Implement support for custom request headers (helpful for POIs, etc.)
|
|
19
|
+
requestHeaders = {}) {
|
|
20
|
+
// TODO (Sampson): Improve rate-limit logic to support self-throttling and n-keys
|
|
21
|
+
// checkRateLimit();
|
|
22
|
+
// Determine URL, and setup parameters
|
|
23
|
+
const url = new URL(`https://api.search.brave.com${typeToPathMap[endpoint]}`);
|
|
24
|
+
const queryParams = new URLSearchParams();
|
|
25
|
+
await log('info', `Preparing to issue request to ${url.toString()}`);
|
|
26
|
+
// TODO (Sampson): Move param-construction/validation to modules
|
|
27
|
+
for (const [key, value] of Object.entries(parameters)) {
|
|
28
|
+
// The 'ids' parameter is expected to appear multiple times for multiple IDs
|
|
29
|
+
if (['localPois', 'localDescriptions'].includes(endpoint)) {
|
|
30
|
+
if (key === 'ids') {
|
|
31
|
+
if (Array.isArray(value) && value.length > 0) {
|
|
32
|
+
value.forEach((id) => queryParams.append(key, id));
|
|
33
|
+
}
|
|
34
|
+
else if (typeof value === 'string') {
|
|
35
|
+
queryParams.set(key, value);
|
|
36
|
+
}
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// Handle result_filter parameter
|
|
41
|
+
if (key === 'result_filter') {
|
|
42
|
+
// Handle special behavior of 'summary' parameter:
|
|
43
|
+
// Requires `result_filter` to be empty, or only contain 'summarizer'
|
|
44
|
+
// see: https://bravesoftware.slack.com/archives/C01NNFM9XMM/p1751654841090929
|
|
45
|
+
if ('summary' in parameters && parameters.summary === true) {
|
|
46
|
+
queryParams.set(key, 'summarizer');
|
|
47
|
+
}
|
|
48
|
+
else if (Array.isArray(value) && value.length > 0) {
|
|
49
|
+
queryParams.set(key, value.join(','));
|
|
50
|
+
}
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
// Handle goggles parameters
|
|
54
|
+
if (key === 'goggles') {
|
|
55
|
+
if (typeof value === 'string') {
|
|
56
|
+
queryParams.set(key, value);
|
|
57
|
+
}
|
|
58
|
+
else if (Array.isArray(value) && value.length > 0) {
|
|
59
|
+
queryParams.set(key, value.join(','));
|
|
60
|
+
}
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (value !== undefined) {
|
|
64
|
+
queryParams.set(key === 'query' ? 'q' : key, value.toString());
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
await log('debug', `Using parameters: ${queryParams.toString()}`);
|
|
68
|
+
// Issue Request
|
|
69
|
+
const urlWithParams = url.toString() + '?' + queryParams.toString();
|
|
70
|
+
const headers = { ...defaultRequestHeaders, ...requestHeaders };
|
|
71
|
+
const response = await fetch(urlWithParams, { headers });
|
|
72
|
+
await log('debug', `Received response from ${urlWithParams}`);
|
|
73
|
+
// Handle Error
|
|
74
|
+
if (!response.ok) {
|
|
75
|
+
let errorMessage = `${response.status} ${response.statusText}`;
|
|
76
|
+
try {
|
|
77
|
+
const responseBody = await response.json();
|
|
78
|
+
errorMessage += `\n${stringify(responseBody, true)}`;
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
errorMessage += `\n${await response.text()}`;
|
|
82
|
+
}
|
|
83
|
+
await log('error', errorMessage);
|
|
84
|
+
// TODO (Sampson): Setup proper error handling, updating state, etc.
|
|
85
|
+
throw new Error(errorMessage);
|
|
86
|
+
}
|
|
87
|
+
// Return Response
|
|
88
|
+
const responseBody = await response.json();
|
|
89
|
+
await log('debug', `Returning response: ${stringify(responseBody, true)}`);
|
|
90
|
+
return responseBody;
|
|
91
|
+
}
|
|
92
|
+
export default {
|
|
93
|
+
issueRequest,
|
|
94
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
const state = {
|
|
3
|
+
transport: 'http',
|
|
4
|
+
port: 8080,
|
|
5
|
+
host: '0.0.0.0',
|
|
6
|
+
braveApiKey: process.env.BRAVE_API_KEY ?? '',
|
|
7
|
+
ready: false,
|
|
8
|
+
};
|
|
9
|
+
export function getOptions() {
|
|
10
|
+
const program = new Command()
|
|
11
|
+
.option('--brave-api-key <string>', 'Brave API key', process.env.BRAVE_API_KEY ?? '')
|
|
12
|
+
.option('--transport <stdio|http>', 'transport type', process.env.BRAVE_MCP_TRANSPORT ?? 'http')
|
|
13
|
+
.option('--port <number>', 'desired port for HTTP transport', process.env.BRAVE_MCP_PORT ?? '8080')
|
|
14
|
+
.option('--host <string>', 'desired host for HTTP transport', process.env.BRAVE_MCP_HOST ?? '0.0.0.0')
|
|
15
|
+
.allowUnknownOption()
|
|
16
|
+
.parse(process.argv);
|
|
17
|
+
const options = program.opts();
|
|
18
|
+
if (!['stdio', 'http'].includes(options.transport)) {
|
|
19
|
+
console.error(`Invalid --transport value: '${options.transport}'. Must be one of: stdio, http.`);
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
if (!options.braveApiKey) {
|
|
23
|
+
console.error('Error: --brave-api-key is required. You can get one at https://brave.com/search/api/.');
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
if (options.transport === 'http') {
|
|
27
|
+
if (options.port < 1 || options.port > 65535) {
|
|
28
|
+
console.error(`Invalid --port value: '${options.port}'. Must be a valid port number between 1 and 65535.`);
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
if (!options.host) {
|
|
32
|
+
console.error('Error: --host is required');
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// Update state
|
|
37
|
+
state.braveApiKey = options.braveApiKey;
|
|
38
|
+
state.transport = options.transport;
|
|
39
|
+
state.port = options.port;
|
|
40
|
+
state.host = options.host;
|
|
41
|
+
state.ready = true;
|
|
42
|
+
return options;
|
|
43
|
+
}
|
|
44
|
+
export default state;
|
package/dist/helpers.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function registerSigIntHandler(transports) {
|
|
2
|
+
process.on('SIGINT', async () => {
|
|
3
|
+
for (const sessionID of transports.keys()) {
|
|
4
|
+
await transports.get(sessionID)?.close();
|
|
5
|
+
transports.delete(sessionID);
|
|
6
|
+
}
|
|
7
|
+
console.error('Server shut down.');
|
|
8
|
+
process.exit(0);
|
|
9
|
+
});
|
|
10
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { getOptions } from './config.js';
|
|
3
|
+
import { stdioServer, httpServer } from './protocols/index.js';
|
|
4
|
+
async function main() {
|
|
5
|
+
const options = getOptions();
|
|
6
|
+
if (!options) {
|
|
7
|
+
console.error('Invalid configuration');
|
|
8
|
+
process.exit(1);
|
|
9
|
+
}
|
|
10
|
+
// default to http server
|
|
11
|
+
if (!options.transport || options.transport === 'http') {
|
|
12
|
+
httpServer.start();
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
// stdio requires explicit request
|
|
16
|
+
if (options.transport === 'stdio') {
|
|
17
|
+
await stdioServer.start();
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
console.error('Invalid transport');
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
main().catch((error) => {
|
|
24
|
+
console.error(error);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import config from '../config.js';
|
|
3
|
+
import { server } from '../server.js';
|
|
4
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
5
|
+
const yieldGenericServerError = (res) => {
|
|
6
|
+
res.status(500).json({
|
|
7
|
+
id: null,
|
|
8
|
+
jsonrpc: '2.0',
|
|
9
|
+
error: { code: -32603, message: 'Internal server error' },
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
export const start = () => {
|
|
13
|
+
if (!config.ready) {
|
|
14
|
+
console.error('Invalid configuration');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
const app = express();
|
|
18
|
+
app.use(express.json());
|
|
19
|
+
app.all('/mcp', async (req, res) => {
|
|
20
|
+
try {
|
|
21
|
+
const transport = new StreamableHTTPServerTransport({
|
|
22
|
+
// Setting to undefined will opt-out of session-id generation
|
|
23
|
+
sessionIdGenerator: undefined,
|
|
24
|
+
});
|
|
25
|
+
await server.connect(transport);
|
|
26
|
+
await transport.handleRequest(req, res, req.body);
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
if (!res.headersSent) {
|
|
30
|
+
yieldGenericServerError(res);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
app.all('/ping', (req, res) => {
|
|
35
|
+
res.status(200).json({ message: 'pong' });
|
|
36
|
+
});
|
|
37
|
+
app.listen(config.port, config.host, () => {
|
|
38
|
+
console.error(`Server is running on http://${config.host}:${config.port}/mcp`);
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
export default { start };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { server } from '../server.js';
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
export const start = async () => {
|
|
4
|
+
const transport = new StdioServerTransport();
|
|
5
|
+
await server.connect(transport);
|
|
6
|
+
console.error('Stdio server started');
|
|
7
|
+
};
|
|
8
|
+
export default { start };
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import tools from './tools/index.js';
|
|
3
|
+
export const server = new McpServer({
|
|
4
|
+
version: '0.1.0',
|
|
5
|
+
name: 'brave-search-mcp-server',
|
|
6
|
+
title: 'Brave Search MCP Server',
|
|
7
|
+
}, {
|
|
8
|
+
capabilities: {
|
|
9
|
+
logging: {},
|
|
10
|
+
tools: { listChanged: false },
|
|
11
|
+
},
|
|
12
|
+
instructions: 'Use this server to search the Web for various types of data ' + 'via the Brave Search API.',
|
|
13
|
+
});
|
|
14
|
+
for (const tool of Object.values(tools)) {
|
|
15
|
+
server.tool(tool.name, tool.description, tool.inputSchema, tool.annotations, tool.execute);
|
|
16
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import params from './params.js';
|
|
2
|
+
import API from '../../BraveAPI/index.js';
|
|
3
|
+
import { log, stringify } from '../../utils.js';
|
|
4
|
+
export const name = 'brave_image_search';
|
|
5
|
+
export const annotations = {
|
|
6
|
+
title: 'Brave Image Search',
|
|
7
|
+
openWorldHint: true,
|
|
8
|
+
};
|
|
9
|
+
export const description = `
|
|
10
|
+
Performs an image search using the Brave Search API. Helpful for when you need pictures of people, places, or things, ideas for graphic design, inspiration for art, or anything else where images are useful. When relaying the results in a markdown-supporting environment, it is helpful to include some/all of the images in the results. Example: .
|
|
11
|
+
`;
|
|
12
|
+
export const execute = async (params) => {
|
|
13
|
+
const content = [];
|
|
14
|
+
const response = await API.issueRequest('images', params);
|
|
15
|
+
for (const { url: page_url, title, thumbnail, properties } of response.results) {
|
|
16
|
+
// Skip results without an image
|
|
17
|
+
if (!thumbnail?.src)
|
|
18
|
+
continue;
|
|
19
|
+
// Prefer property URL as it is the shortest-possible URL
|
|
20
|
+
const image_url = properties?.url ?? thumbnail.src;
|
|
21
|
+
const fetched_image = await fetchImage(image_url);
|
|
22
|
+
if (fetched_image) {
|
|
23
|
+
const { mimeType, data } = fetched_image;
|
|
24
|
+
content.push({ type: 'text', text: stringify({ title, page_url, image_url }) }, { type: 'image', mimeType, data });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return { content, isError: false };
|
|
28
|
+
};
|
|
29
|
+
async function fetchImage(url) {
|
|
30
|
+
await log('info', `Fetching image data from ${url}`);
|
|
31
|
+
try {
|
|
32
|
+
const response = await fetch(url);
|
|
33
|
+
const buffer = await response.arrayBuffer();
|
|
34
|
+
return {
|
|
35
|
+
data: Buffer.from(buffer).toString('base64'),
|
|
36
|
+
mimeType: response.headers.get('content-type') ?? 'image/jpeg',
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
await log('error', `Error fetching image data from ${url}: ${error}`);
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export default {
|
|
45
|
+
name,
|
|
46
|
+
description,
|
|
47
|
+
annotations,
|
|
48
|
+
inputSchema: params.shape,
|
|
49
|
+
execute,
|
|
50
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const params = z.object({
|
|
3
|
+
query: z
|
|
4
|
+
.string()
|
|
5
|
+
.min(1)
|
|
6
|
+
.max(400)
|
|
7
|
+
.refine((str) => str.split(/\s+/).length <= 50, 'Query cannot exceed 50 words')
|
|
8
|
+
.describe("The user's search query. Query cannot be empty. Limited to 400 characters and 50 words."),
|
|
9
|
+
country: z
|
|
10
|
+
.string()
|
|
11
|
+
.default('US')
|
|
12
|
+
.describe('Search query country, where the results come from. The country string is limited to 2 character country codes of supported countries.')
|
|
13
|
+
.optional(),
|
|
14
|
+
search_lang: z
|
|
15
|
+
.string()
|
|
16
|
+
.default('en')
|
|
17
|
+
.describe('Search language preference. The 2 or more character language code for which the search results are provided.')
|
|
18
|
+
.optional(),
|
|
19
|
+
count: z
|
|
20
|
+
.number()
|
|
21
|
+
.int()
|
|
22
|
+
.min(1)
|
|
23
|
+
.max(200)
|
|
24
|
+
.default(50)
|
|
25
|
+
.describe('Number of results (1-200, default 50). Combine this parameter with `offset` to paginate search results.')
|
|
26
|
+
.optional(),
|
|
27
|
+
safesearch: z
|
|
28
|
+
.union([z.literal('off'), z.literal('strict')])
|
|
29
|
+
.default('strict')
|
|
30
|
+
.describe("Filters search results for adult content. The following values are supported: 'off' - No filtering. 'strict' - Drops all adult content from search results.")
|
|
31
|
+
.optional(),
|
|
32
|
+
spellcheck: z
|
|
33
|
+
.boolean()
|
|
34
|
+
.default(true)
|
|
35
|
+
.describe('Whether to spellcheck provided query.')
|
|
36
|
+
.optional(),
|
|
37
|
+
});
|
|
38
|
+
export default params;
|