UNPKG

mcp-brave-search

Version:

Brave Search MCP Server - Web and News Search via stdio

234 lines 9.22 kB
#!/usr/bin/env node /** * 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