@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 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
+ [![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-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) [![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-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
+ [![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-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) [![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-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;
@@ -0,0 +1,4 @@
1
+ export const RATE_LIMIT = {
2
+ perSecond: 1,
3
+ perMonth: 15000,
4
+ };
@@ -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,2 @@
1
+ export { default as stdioServer } from './stdio.js';
2
+ export { default as httpServer } from './http.js';
@@ -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: ![Image Description](image_url).
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;