mcp-brave-search
Version:
Brave Search MCP Server - Web and News Search via stdio
234 lines • 9.22 kB
JavaScript
/**
* Brave Search MCP Server
* Provides Web Search and News Search capabilities via stdio
*/
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
import { BraveSearchAPI } from './brave-api.js';
// Configuration
const getConfig = () => {
const apiKey = process.env.BRAVE_API_KEY;
if (!apiKey) {
throw new Error('BRAVE_API_KEY environment variable is required. ' +
'Get your API key from https://api.search.brave.com/');
}
return {
apiKey,
baseUrl: process.env.BRAVE_API_BASE_URL,
};
};
// Define available tools
const TOOLS = [
{
name: 'brave_web_search',
description: 'Search the web using Brave Search API. Returns web search results with titles, URLs, and descriptions. ' +
'Use this for general web searches, finding information, or researching topics.',
inputSchema: {
type: 'object',
properties: {
q: {
type: 'string',
description: 'Search query (required, max 400 characters, 50 words)',
},
count: {
type: 'number',
description: 'Number of results to return (1-20, default: 10)',
minimum: 1,
maximum: 20,
},
offset: {
type: 'number',
description: 'Pagination offset (0-9, default: 0)',
minimum: 0,
maximum: 9,
},
country: {
type: 'string',
description: 'Country code for search results (e.g., "US", "GB", "CN")',
},
search_lang: {
type: 'string',
description: 'Search language code (e.g., "en", "zh", "es")',
},
ui_lang: {
type: 'string',
description: 'UI language (e.g., "en-US", "zh-CN")',
},
safesearch: {
type: 'string',
enum: ['off', 'moderate', 'strict'],
description: 'Safe search filter level (default: "moderate")',
},
freshness: {
type: 'string',
description: 'Time filter: "pd" (24h), "pw" (week), "pm" (month), "py" (year), or custom range like "2024-01-01to2024-12-31"',
},
text_decorations: {
type: 'boolean',
description: 'Enable text decorations in results (default: true)',
},
spellcheck: {
type: 'boolean',
description: 'Enable spellcheck for query (default: true)',
},
},
required: ['q'],
},
},
{
name: 'brave_news_search',
description: 'Search for news articles using Brave Search API. Returns recent news with titles, URLs, descriptions, and source information. ' +
'Use this for finding current news, breaking stories, or recent events.',
inputSchema: {
type: 'object',
properties: {
q: {
type: 'string',
description: 'Search query (required, max 400 characters, 50 words)',
},
count: {
type: 'number',
description: 'Number of results to return (1-20, default: 10)',
minimum: 1,
maximum: 20,
},
offset: {
type: 'number',
description: 'Pagination offset (0-9, default: 0)',
minimum: 0,
maximum: 9,
},
country: {
type: 'string',
description: 'Country code for news results (e.g., "US", "GB", "CN")',
},
search_lang: {
type: 'string',
description: 'Search language code (e.g., "en", "zh", "es")',
},
ui_lang: {
type: 'string',
description: 'UI language (e.g., "en-US", "zh-CN")',
},
safesearch: {
type: 'string',
enum: ['off', 'moderate', 'strict'],
description: 'Safe search filter level (default: "moderate")',
},
freshness: {
type: 'string',
description: 'Time filter: "pd" (24h), "pw" (week), "pm" (month), "py" (year)',
},
spellcheck: {
type: 'boolean',
description: 'Enable spellcheck for query (default: true)',
},
},
required: ['q'],
},
},
];
// Main server implementation
async function main() {
const config = getConfig();
const braveAPI = new BraveSearchAPI(config);
const server = new Server({
name: 'brave-search-mcp-server',
version: '1.0.0',
}, {
capabilities: {
tools: {},
},
});
// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
return { tools: TOOLS };
});
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
if (name === 'brave_web_search') {
const params = args;
if (!params.q || params.q.trim().length === 0) {
throw new Error('Search query "q" is required and cannot be empty');
}
const results = await braveAPI.webSearch(params);
// Format results for better readability
const webResults = results.web?.results || [];
const formattedResults = webResults.map((result, index) => ({
position: index + 1,
title: result.title,
url: result.url,
description: result.description || 'No description available',
age: result.age,
}));
return {
content: [
{
type: 'text',
text: JSON.stringify({
query: results.query.original,
total_results: webResults.length,
results: formattedResults,
}, null, 2),
},
],
};
}
if (name === 'brave_news_search') {
const params = args;
if (!params.q || params.q.trim().length === 0) {
throw new Error('Search query "q" is required and cannot be empty');
}
const results = await braveAPI.newsSearch(params);
// Format results for better readability
const formattedResults = results.results.map((result, index) => ({
position: index + 1,
title: result.title,
url: result.url,
description: result.description || 'No description available',
age: result.age,
source: result.meta_url?.hostname,
}));
return {
content: [
{
type: 'text',
text: JSON.stringify({
query: results.query.original,
total_results: results.results.length,
results: formattedResults,
}, null, 2),
},
],
};
}
throw new Error(`Unknown tool: ${name}`);
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
content: [
{
type: 'text',
text: `Error: ${errorMessage}`,
},
],
isError: true,
};
}
});
// Start server with stdio transport
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('Brave Search MCP Server running on stdio');
console.error('Available tools: brave_web_search, brave_news_search');
}
main().catch((error) => {
console.error('Fatal error:', error);
process.exit(1);
});
//# sourceMappingURL=index.js.map